Room для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Room и зачем он нужен?

Room — это библиотека для работы с базами данных в Android-приложениях, написанных на Kotlin или Java. Она предоставляет слой абстракции поверх SQLite, позволяя работать с базой данных через объектно-ориентированный подход, а не через сырые SQL-запросы.

Основные преимущества Room:

  • Минимизация шаблонного кода — Room автоматически генерирует код для работы с SQLite на основе аннотаций.
  • Проверка SQL-запросов на этапе компиляции — ошибки в запросах обнаруживаются до запуска приложения.
  • Интеграция с Coroutines и Flow — поддержка реактивного программирования и асинхронных операций.
  • Миграции базы данных — встроенные механизмы для обновления схемы без потери данных.
  • TypeConverters — возможность хранить сложные типы данных (например, Date, List) в базе.

Room идеально подходит для приложений, которым необходимо локальное хранение структурированных данных: заметки, кэш, пользовательские настройки, офлайн-режим и т.д.

Установка Room

Room не устанавливается через pip или npm, так как это библиотека для Android. Добавление в проект происходит через файл build.gradle (уровня модуля).

Шаг 1: Добавьте плагин KSP (Kotlin Symbol Processing)

В корневом build.gradle добавьте плагин KSP:

plugins {
    id 'com.google.devtools.ksp' version '1.9.0-1.0.13' apply false
}

Шаг 2: Подключите Room в модуле приложения

В build.gradle модуля app добавьте:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.devtools.ksp'
}

android {
    // ... ваша конфигурация
}

dependencies {
    def room_version = "2.6.1"

    implementation "androidx.room:room-runtime:$room_version"
    ksp "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version" // Для Coroutines
}

После синхронизации Gradle библиотека готова к использованию.

Быстрый старт с примером кода

Создадим простое приложение для хранения списка задач (Todo).

Шаг 1: Определите сущность (Entity)

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "tasks")
data class Task(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val title: String,
    val description: String,
    val isCompleted: Boolean = false
)

Шаг 2: Создайте DAO (Data Access Object)

import androidx.room.*
import kotlinx.coroutines.flow.Flow

@Dao
interface TaskDao {
    @Query("SELECT * FROM tasks ORDER BY id DESC")
    fun getAllTasks(): Flow<List<Task>>

    @Insert
    suspend fun insertTask(task: Task)

    @Update
    suspend fun updateTask(task: Task)

    @Delete
    suspend fun deleteTask(task: Task)
}

Шаг 3: Создайте базу данных

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun taskDao(): TaskDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

Шаг 4: Используйте в Activity или ViewModel

class MainActivity : AppCompatActivity() {
    private lateinit var db: AppDatabase
    private lateinit var taskDao: TaskDao

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = AppDatabase.getDatabase(this)
        taskDao = db.taskDao()

        // Пример: вставка задачи
        lifecycleScope.launch {
            taskDao.insertTask(Task(title = "Купить молоко", description = "В магазине на углу"))
        }

        // Наблюдение за списком задач
        taskDao.getAllTasks().observe(this) { tasks ->
            // Обновление UI
        }
    }
}

Основные методы и функции Room

1. @Insert — вставка данных

Что делает: Вставляет одну или несколько записей в таблицу.

Параметры:

  • entity — объект сущности или коллекция объектов.
  • onConflict — стратегия при конфликте (REPLACE, ABORT, IGNORE).
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTask(task: Task)

@Insert
suspend fun insertTasks(tasks: List<Task>)

2. @Update — обновление данных

Что делает: Обновляет существующую запись по первичному ключу.

Параметры: объект сущности с заполненным первичным ключом.

@Update
suspend fun updateTask(task: Task)

3. @Delete — удаление данных

Что делает: Удаляет запись по первичному ключу.

Параметры: объект сущности.

@Delete
suspend fun deleteTask(task: Task)

4. @Query — произвольный SQL-запрос

Что делает: Выполняет любой SQL-запрос. Возвращает результат в виде объекта, списка или Flow.

Параметры: SQL-запрос с возможными параметрами через :.

@Query("SELECT * FROM tasks WHERE isCompleted = :isCompleted")
fun getTasksByStatus(isCompleted: Boolean): Flow<List<Task>>

@Query("SELECT * FROM tasks WHERE id = :taskId")
suspend fun getTaskById(taskId: Int): Task?

5. @Transaction — атомарные операции

Что делает: Гарантирует выполнение нескольких операций как единого целого.

Параметры: аннотация для метода DAO.

@Transaction
suspend fun moveTaskToCompleted(task: Task) {
    updateTask(task.copy(isCompleted = true))
    // Дополнительные операции
}

6. @TypeConverter — конвертация типов

Что делает: Позволяет хранить сложные типы (например, Date, List) в базе.

Параметры: статические методы с аннотациями @TypeConverter.

class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time
    }
}

// Подключение в базе данных:
@Database(entities = [Task::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { ... }

7. @ForeignKey — внешние ключи

Что делает: Определяет связи между таблицами.

Параметры: entity, parentColumns, childColumns, onDelete, onUpdate.

@Entity(
    tableName = "subtasks",
    foreignKeys = [ForeignKey(
        entity = Task::class,
        parentColumns = ["id"],
        childColumns = ["taskId"],
        onDelete = ForeignKey.CASCADE
    )]
)
data class SubTask(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val taskId: Int,
    val title: String
)

8. @Index — индексы для ускорения поиска

Что делает: Создает индекс для указанных колонок.

Параметры: массив колонок, уникальность.

@Entity(
    tableName = "users",
    indices = [Index(value = ["email"], unique = true)]
)
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val email: String,
    val name: String
)

9. @Embedded — вложенные объекты

Что делает: Позволяет хранить поля вложенного объекта в той же таблице.

Параметры: префикс для колонок (опционально).

data class Address(
    val street: String,
    val city: String
)

@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: Int,
    val name: String,
    @Embedded val address: Address
)

10. @RawQuery — выполнение сырых SQL-запросов

Что делает: Позволяет выполнять динамические SQL-запросы, которые не известны на этапе компиляции.

Параметры: объект SupportSQLiteQuery.

@RawQuery(observedEntities = [Task::class])
fun getTasksFromRawQuery(query: SupportSQLiteQuery): Flow<List<Task>>

// Использование:
val query = SimpleSQLiteQuery("SELECT * FROM tasks WHERE title LIKE '%' || ? || '%'", arrayOf("молоко"))
taskDao.getTasksFromRawQuery(query)

Реальный практический пример использования

Создадим приложение для ведения дневника питания с возможностью поиска по дате.

Сущность FoodEntry

@Entity(tableName = "food_entries")
data class FoodEntry(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val foodName: String,
    val calories: Int,
    val date: Long // timestamp
)

DAO с расширенными запросами

@Dao
interface FoodDao {
    @Query("SELECT * FROM food_entries ORDER BY date DESC")
    fun getAllEntries(): Flow<List<FoodEntry>>

    @Query("SELECT * FROM food_entries WHERE date BETWEEN :startDate AND :endDate")
    fun getEntriesBetweenDates(startDate: Long, endDate: Long): Flow<List<FoodEntry>>

    @Query("SELECT SUM(calories) FROM food_entries WHERE date = :date")
    fun getTotalCaloriesForDate(date: Long): Flow<Int>

    @Insert
    suspend fun insertEntry(entry: FoodEntry)

    @Delete
    suspend fun deleteEntry(entry: FoodEntry)
}

ViewModel для управления данными

class FoodViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDatabase.getDatabase(application).foodDao()

    val allEntries: Flow<List<FoodEntry>> = dao.getAllEntries()

    fun getEntriesForDate(date: Long): Flow<List<FoodEntry>> {
        val startOfDay = // вычисление начала дня
        val endOfDay = // вычисление конца дня
        return dao.getEntriesBetweenDates(startOfDay, endOfDay)
    }

    fun addEntry(foodName: String, calories: Int) {
        viewModelScope.launch {
            dao.insertEntry(FoodEntry(
                foodName = foodName,
                calories = calories,
                date = System.currentTimeMillis()
            ))
        }
    }
}

Советы и типичные ошибки

Советы:

  • Используйте Flow для реактивного обновления UI — Room автоматически уведомляет об изменениях в данных.
  • Всегда используйте suspend функции для операций записи — это предотвращает блокировку главного потока.
  • Создавайте миграции для изменения схемы — используйте Migration классы вместо fallbackToDestructiveMigration().
  • Используйте KSP вместо KAPT — KSP работает быстрее и лучше поддерживает Kotlin.
  • Тестируйте DA

Рекомендаций