Room 3.0 — это обновление основной версии, которое переводит библиотеку на Kotlin-first. Она поддерживает Kotlin Multiplatform (KMP), требует Kotlin Symbol Processing (KSP) и использует сопрограммы для асинхронных операций.
Чтобы избежать проблем совместимости с существующими приложениями Room 2.x и транзитивными зависимостями, Room 3.0 находится в новом пакете: androidx.room3.
В этом руководстве описаны шаги, необходимые для переноса существующей реализации Room 2.x в Room 3.0.
Основные изменения в Room 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заменяет исполнителей. - Нет SupportSQLite. API
SQLiteDriverподдерживают Room. Room удаляетSupportSQLiteDatabaseиз основных API. - Изменения API. При переносе и обратных вызовах базы данных используется
SQLiteConnectionвместоSupportSQLiteDatabase. - Конвертеры для реактивных типов. Для типов возвращаемых значений RxJava, LiveData, Guava и Paging требуется зарегистрировать
@DaoReturnTypeConverters.
Мы рекомендуем выполнить миграцию в два этапа: сначала подготовить и модернизировать базу кода в Room 2.x, а затем перейти на Room 3.0.
Подготовка и модернизация в Room 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.
В файле
build.gradle.ktsмодуля примените плагин KSP:plugins { id("com.google.devtools.ksp") version "<ksp_version>" }Убедитесь, что версия KSP совместима с вашей версией Kotlin.
Замените
kaptилиannotationProcessorнаkspдля зависимости компилятора Room:dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
Используйте сопрограммы
Для асинхронных операций в Room 3.0 требуются сопрограммы.
- Обновите объекты доступа к данным. Если они не возвращают наблюдаемый реактивный тип, например
Flowили типы RxJava, все функции объектов доступа к данным должны быть функциямиsuspend.
// 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 драйверов и избегайте Support SQLite
Room 3.0 полностью поддерживается SQLiteDriver и больше не поддерживает SupportSQLiteDatabase в своих основных API.
Если вы не вызовете setDriver, чтобы задать SQLiteDriver в построителе базы данных, Room 2.8 будет работать в режиме совместимости, в котором функционируют API Support SQLite и Driver. Этот режим совместимости позволяет постепенно преобразовывать базу кода перед включением драйвера.
- Преобразование переносов. Перенесите подклассы
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, наuseWriterConnectionиimmediateTransaction.
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (useWriterConnection - Room 2.8)
import androidx.room.useWriterConnection
import androidx.room.immediateTransaction
db.useWriterConnection { connection ->
connection.immediateTransaction {
// perform database operations
}
}
- Избегайте прямого использования
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 в Driver 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()
}
Переход на Room 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()
Обновите обратные вызовы, чтобы приостановить функции
В Room 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.
Если вы ещё не перешли на потоки сопрограмм на этапе 1, вам нужно перенести все экземпляры Observer в createFlow.
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}