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