Course Content
-
Разработка приложения Фитнес-календарь
- Описание проекта
- Разработка графического интерфейса для отображения списка запланированных тренировок
- Создание уведомления Notification
- Создание PendingIntent
- Разработка интерфейса для создания напоминания о тренировке
- Создание локальной БД для хранения информации о тренировках
- Создание BroadcastReceiver
- Создание каналов уведомлений
- Доработка методов для отображения уведомлений
- Описание Alarm Manager
- Реализация механизма напоминания через AlarmManager
- Cоздание PendingIntent и добавление его к AlarmManager.
- Выводы
Создание локальной БД для хранения информации о тренировках
Для хранения информации о датах тренировок нам нужно будет создать локальное хранилище. Для этого мы воспользуемся Room. Если вы никогда не работали с Room, то более подробно можете прочитать в этом мини-курсе Там описаны базовые понятия и по шагам реализовано взаимодействие в БД
Для того, чтобы использовать Room необходимо
- Добавить нужные зависимости
- Определить Entity, Dao и саму базу данных
- Реализовать нужные методы в Dao
Добавляем нужные библиотеки
В блок dependencies в файле build.gradle вставьте следующие зависимости. Чтобы не дублировать константы версий одной и тоже библиотеки рекомендуется выносить их в отдельный блок, хранящий версии библиотек. И потом ссылаться на них. Пример $rootProject.roomVersion
Более подробно прочитать, как хранить в порядке список библиотек в gradle-файлах вы можете прочитать в статье Наводим порядок в build.gradle
// Room components implementation "androidx.room:room-runtime:$rootProject.roomVersion" implementation "androidx.room:room-ktx:$rootProject.roomVersion" kapt "androidx.room:room-compiler:$rootProject.roomVersion" androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
Определяем Entity
Для хранения данных в БД нам нужна таблица – или Entity в терминах Room. В качестве Entity мы будем использовать класс ReminderData, который будет хранить информацию о тренировках и датах напоминаний.
@Entity(tableName = "reminder_data") @Parcelize data class ReminderData( @PrimaryKey(autoGenerate = true) var id: Long = 0, var name: String? = null, var type: WorkoutType = WorkoutType.Swimming, var hour: Int = 0, var minute: Int = 0, var days: List<String?>? = null ) : Parcelable
Я не буду повторять, что значит каждая из аннотаций – это всё отлично расписано и можете прочитать в этом мини-курсе
Создание DAO
Для управления записями таблицы, необходимо создать Data Access Object
@Dao interface ReminderDao { @Query("SELECT * from reminder_data") fun getReminderData(): List<ReminderData> @Query("SELECT * from reminder_data where id= :id") fun getReminderById(id: Long): ReminderData? @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(reminder: ReminderData): Long @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(reminders: List<ReminderData>) @Update(onConflict = OnConflictStrategy.REPLACE) fun update(reminder: ReminderData) @Query("DELETE FROM reminder_data") fun deleteAll() @Query("DELETE FROM reminder_data where id LIKE :id") fun deleteById(id: Long) }
Создание Базы Данных
@Database(entities = [ReminderData::class], version = 1) @TypeConverters(WorkoutTypeConverter::class, DaysConverter::class) abstract class WorkoutDatabase : RoomDatabase() { abstract fun reminderDataDao(): ReminderDao }
Здесь указаны так называемые конверторы – они необходимы для сохранения объектов у которых сложный тип данных.
Создание конверторов данных
Для сохранения типов тренировки (Бег/Плавание/Езда на велосипеде) нужно написать соответствующий конвертор данных
class WorkoutTypeConverter { @TypeConverter fun toStatus(numeral: Int): WorkoutType { return when (numeral) { 0 -> WorkoutType.Swimming 1 -> WorkoutType.Cycling 2 -> WorkoutType.Running else -> WorkoutType.Swimming } } @TypeConverter fun fromStatus(type: WorkoutType): Int { return when (type) { WorkoutType.Swimming -> 0 WorkoutType.Cycling -> 1 WorkoutType.Running -> 2 } } }
Кроме этого, нам необходимо сохранять список дней, по которым будет проходить тренировка – соответственно необходим конвертор из спика строк в строку – это самый простой способ сохранить список. В противном случае необходимо создать ещё одну таблицу в которой будут храниться дни и реализовать отношение 1 ко многим или многие ко многим. Для простоты сохраним данные в строку
const val SEPARATOR = ";" class DaysConverter { @TypeConverter fun toDaysString(days: List<String>): String { return days.joinToString(SEPARATOR) } @TypeConverter fun fromDaysString(days: String): List<String> { return days.split(SEPARATOR).toList() } }
Инициализация Базы Данных
Для того, чтобы создать БД и использовать для сохранения/чтения данных добавьте следующий код в класс FitnessApp
class FitnessApp : Application() { lateinit var app: FitnessApp private lateinit var database: WorkoutDatabase override fun onCreate() { super.onCreate() app = this database = Room.databaseBuilder(this, WorkoutDatabase::class.java, "database") // Только для примера, в реальном проекте так не стоит делать .allowMainThreadQueries() .build() ..... } fun getInstance(): FitnessApp { return app } fun getDatabase(): WorkoutDatabase { return database } }
Метод .allowMainThreadQueries() – позволяет обращаться к БД в главном потоке, в реальном приложении этого стоит избегать – так как операции чтения из БД могут быть долгими и вы получите ANR
Сохранение ReminderData
Для сохранения данных в БД после нажатия на кнопку сохранить необходимо добавить следующий код:
private fun createReminder(name: String, dateType: WorkoutType, days: List<String?>?): Long { reminderData.name = name reminderData.type = dateType reminderData.days = days return ReminderLocalRepostiory(activity?.applicationContext).saveReminder(reminderData) }
Для чтения и сохранения данных из БД давайте создадим репозиторий ReminderLocalRepostiory
Метод saveReminder() определен в репозитории
class ReminderLocalRepostiory(val context: Context?) { private val roomDatabase = (context?.applicationContext as FitnessApp).getDatabase() private val dao = roomDatabase.reminderDataDao() fun getReminders(): List<ReminderData> { return dao.getReminderData() } fun saveReminder(reminderData:ReminderData):Long{ return dao.insert(reminderData) } }
Чтение ReminderData из БД
Для чтения данных из БД давайте создадим метод getReminders()
class ReminderLocalRepostiory(val context: Context?) { private val roomDatabase = (context?.applicationContext as FitnessApp).getDatabase() private val dao = roomDatabase.reminderDataDao() fun getReminders(): List<ReminderData> { return dao.getReminderData() } }
Чтение данных в FirstFragment теперь будет выглядеть так
mainAdapter = MainAdapter(this, ReminderLocalRepostiory(activity?.applicationContext).getReminders())
Отлично! Теперь наше приложение умеет сохранять информацию о дате и времени тренировок и отображать её пользователю. В следующих уроках эта информация понадобится для того, чтобы запланировать уведомление и напомнить пользователю о тренировке.
Результат этого урока вы можете найти по ссылке в ветке feature/04-create-database