Переход с Room 2.x на Room 3.0

Room 3.0 — это крупное обновление версии, в котором библиотека переходит на Kotlin-ориентированный подход. Она поддерживает Kotlin Multiplatform (KMP), требует Kotlin Symbol Processing (KSP) и обеспечивает использование сопрограмм для асинхронных операций.

Чтобы предотвратить проблемы совместимости с существующими приложениями Room 2.x и транзитивными зависимостями, Room 3.0 размещен в новом пакете: androidx.room3 .

В этом руководстве описаны шаги, необходимые для миграции вашей существующей системы Room 2.x в систему Room 3.0.

Ключевые изменения в комнате 3.0

Перед началом миграции ознакомьтесь с основными отличиями:

  • Новый пакет и артефакты : Все классы находятся в androidx.room3 . Артефакты используют префикс room3 , например, androidx.room3:room3-runtime .
  • Только Kotlin и KSP : Room 3.0 не поддерживает генерацию кода Java. Используйте KSP вместо KAPT или обработчиков аннотаций Java. Room 3.0 по-прежнему поддерживает исходный код Java в качестве входных данных.
  • Сначала сопрограммы : функции DAO должны быть функциями suspend , за исключением наблюдаемых типов. CoroutineContext заменяет исполнители.
  • Нет поддержки SQLite : API SQLiteDriver поддерживают Room. Room удаляет SupportSQLiteDatabase из основных API.
  • Изменения в API : Для миграций и обратных вызовов к базе данных используется SQLiteConnection вместо SupportSQLiteDatabase .
  • Для конвертеров реактивных типов : возвращаемых типов RxJava, LiveData, Guava и Paging необходимо зарегистрировать аннотацию @DaoReturnTypeConverters .

Мы рекомендуем миграцию в два отдельных этапа: сначала подготовка и модернизация вашей кодовой базы в Room 2.x, а затем переход на Room 3.0.


Подготовка и модернизация в комнате 2.x

Перед переходом на Room 3.0 большую часть работ по модернизации можно выполнить, обновив систему до текущей версии Room 2.x, например, Room 2.8. Room 2.8 поддерживает Kotlin Multiplatform (KMP) и включает в себя множество API драйверов, используемых в Room 3.0.

Обновите до версии Room 2.8 и выше.

Обновите конфигурацию сборки, чтобы использовать текущую версию Room 2.x:

[versions]
room2 = "2.8.4" # Use the current Room 2.8 version

[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room2" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room2" }

Переход с KAPT на KSP

В Room 3.0 не поддерживаются обработчики аннотаций Java или KAPT. Необходимо использовать Kotlin Symbol Processing (KSP). Переход на эту технологию возможен, пока вы используете Room 2.x.

  1. В build.gradle.kts вашего модуля примените плагин KSP:

    plugins {
        id("com.google.devtools.ksp") version "<ksp_version>"
    }
    

    Убедитесь, что версия KSP совместима с вашей версией Kotlin.

  2. Замените kapt или annotationProcessor на ksp для зависимости компилятора Room:

    dependencies {
        implementation(libs.androidx.room.runtime)
        ksp(libs.androidx.room.compiler)
    }
    

Внедрить сопрограммы

Для асинхронных операций в Room 3.0 требуются сопрограммы.

  • Обновите ваши DAO: если они не возвращают наблюдаемый реактивный тип, например, типы Flow или RxJava, все функции DAO должны быть функциями suspend functions).
// Before (Blocking)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAll(): List<User>
}

// After (Suspend)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    suspend fun getAll(): List<User>
}
  • Если вы настроили RoomDatabase с использованием пользовательского Executor для выполнения операций с базой данных, перейдите на CoroutineContext , используя setQueryCoroutineContext в построителе:
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

Используйте API драйверов и избегайте поддержки SQLite.

В Room 3.0 полностью используется SQLiteDriver , и в основных API больше не поддерживается SupportSQLiteDatabase .

Если вы не вызываете setDriver для установки SQLiteDriver в вашем построителе базы данных, Room 2.8 работает в режиме совместимости, в котором функционируют как API поддержки SQLite, так и API драйвера. Этот режим совместимости позволяет постепенно преобразовывать ваш код перед включением драйвера.

  • Преобразование миграций : переведите ваши подклассы Migration и AutoMigrationSpec на использование SQLiteConnection вместо SupportSQLiteDatabase .
// Before (SupportSQLiteDatabase)
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}

// After (SQLiteConnection - Room 2.8)
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(connection: SQLiteConnection) {
        connection.execSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • Преобразование обратных вызовов базы данных : Обновите реализации RoomDatabase.Callback для использования SQLiteConnection :
// Before (SupportSQLiteDatabase)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(db: SupportSQLiteDatabase) {
        // ...
    }
}

// After (SQLiteConnection - Room 2.8)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}
  • Преобразование функций DAO с аннотацией @RawQuery : Для функций, аннотированных @RawQuery , используйте RoomRawQuery вместо SupportSQLiteQuery :
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

// After (RoomRawQuery)
@Dao
interface UserDao {
    @RawQuery
    suspend fun getUser(query: RoomRawQuery): User
}

Вы можете создать объект RoomRawQuery во время выполнения программы:

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • Преобразование API транзакций : Замените блоки withTransaction и runInTransaction , используемые только в Android, на withWriteTransaction или withReadTransaction :
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction

db.withWriteTransaction {
    // perform database operations
}

Если вам необходим прямой низкоуровневый доступ к соединению транзакции, вы также можете использовать useWriterConnection с immediateTransaction .

  • Избегайте прямого использования SupportSQLiteDatabase : если у вас есть большой объем устаревшего кода, который по-прежнему требует SupportSQLiteDatabase , и вы пока не можете его перенести, используйте артефакт совместимости androidx.room:room-sqlite-wrapper :
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

Затем используйте getSupportWrapper для получения объекта SupportSQLiteDatabase из вашего экземпляра базы данных Room:

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • Настройка драйвера SQLite : После переноса всех способов использования Room API на API драйверов, настройте драйвер, например BundledSQLiteDriver или AndroidSQLiteDriver , вызвав setDriver в вашем конструкторе RoomDatabase :
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

val db = Room.databaseBuilder<AppDatabase>(context, "db")
    .setDriver(BundledSQLiteDriver())
    .build()

Внедрить отслеживание аннулирования на основе потока данных.

В Room 2.8 представлен API InvalidationTracker.createFlow . Используйте этот API для перехода от устаревших реализаций InvalidationTracker.Observer , оставаясь при этом в Room 2.x. Это подготовит ваш код к Room 3.0, в котором Observer будет полностью удален.

// Before (InvalidationTracker.Observer)
val observer = object : InvalidationTracker.Observer("User") {
    override fun onInvalidated(tables: Set<String>) {
        // reload user data
    }
}
db.invalidationTracker.addObserver(observer)

// After (createFlow - Room 2.8)
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}

Переместиться в комнату 3.0

После модернизации вашего приложения на Room 2.x переход на Room 3.0 включает в себя обновление зависимостей, импорт пакетов и обратных вызовов базы данных.

Обновите зависимости и импорт пакетов.

  • В конфигурации сборки замените зависимости androidx.room на androidx.room3 :
[versions]
room3 = "3.0.0" # Use the current Room 3.0 version

[libraries]
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
  • Обновите блок зависимостей:
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • Обновите импорт пакетов. Замените import androidx.room.* на import androidx.room3.* .

Обновить API преобразователей типов

В версии Room 3.0 переименованы API преобразователей типов, чтобы уточнить их использование для преобразования значений столбцов и избежать путаницы с преобразователями типов возвращаемых значений DAO.

Обновите следующие аннотации и функции в вашем коде:

  • Переименуйте @TypeConverter в @ColumnTypeConverter .
  • Переименуйте @TypeConverters в @ColumnTypeConverters .
  • Переименуйте @ProvidedTypeConverter в @ProvidedColumnTypeConverter .
  • Переименуйте RoomDatabase.Builder.addTypeConverter в addColumnTypeConverter .

Пример:

// Before
@ProvidedTypeConverter
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addTypeConverter(convertersInstance)
  .build()
// After
import androidx.room3.ColumnTypeConverter
import androidx.room3.ColumnTypeConverters
import androidx.room3.ProvidedColumnTypeConverter

@ProvidedColumnTypeConverter
class Converters {
  @ColumnTypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addColumnTypeConverter(convertersInstance)
  .build()

Обновите функции обратного вызова, чтобы приостанавливать их работу.

В комнате 3.0 функции обратного вызова и миграции базы данных используют SQLiteConnection и являются suspend функциями.

  • Обновите классы ручной Migration :
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.async.executeSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override suspend fun migrate(connection: SQLiteConnection) {
        connection.executeSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • Обновите реализацию метода RoomDatabase.Callback :
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

Зарегистрируйте преобразователи типов возврата DAO.

В Room 3.0 для реактивных типов возвращаемых значений, таких как RxJava, LiveData, Guava и Paging, требуется регистрация преобразователей типов возвращаемых значений DAO с помощью @DaoReturnTypeConverters .

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • Paging ( PagingSource ) : Зарегистрируйте PagingSourceDaoReturnTypeConverter из артефакта androidx.room3:room3-paging .
  • RxJava ( Observable , Flowable , Single , Maybe , Completable ) : Зарегистрируйте RxDaoReturnTypeConverters из артефакта androidx.room3:room3-rxjava3 .
  • Guava ( ListenableFuture ) : Зарегистрируйте GuavaDaoReturnTypeConverter из артефакта androidx.room3:room3-guava .
  • LiveData ( LiveData ) : Зарегистрируйте LiveDataDaoReturnTypeConverter из артефакта androidx.room3:room3-livedata .

Проверка удаления наблюдателя InvalidationTracker

В Room 3.0 полностью удалены InvalidationTracker.Observer и связанные с ним методы регистрации, такие как addObserver и removeObserver .

Если вы еще не перешли на сопрограммы на первом этапе , вам необходимо перенести все случаи использования Observer в createFlow :

val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}