Room 3.0 est une mise à jour de version majeure qui fait passer la bibliothèque à une approche Kotlin-first. Elle est compatible avec Kotlin Multiplatform (KMP), nécessite le traitement des symboles Kotlin (KSP) et applique les coroutines pour les opérations asynchrones.
Pour éviter les problèmes de compatibilité avec les applications Room 2.x existantes et les dépendances transitives, Room 3.0 réside dans un nouveau package : androidx.room3.
Ce guide décrit les étapes nécessaires pour migrer votre implémentation Room 2.x existante vers Room 3.0.
Principaux changements dans Room 3.0
Avant de commencer la migration, familiarisez-vous avec les principales différences :
- Nouveau package et nouveaux artefacts : toutes les classes résident dans
androidx.room3. Les artefacts utilisent leroom3préfixe, tel queandroidx.room3:room3-runtime. - Kotlin et KSP uniquement : Room 3.0 n'est pas compatible avec la génération de code Java. Utilisez KSP au lieu de KAPT ou de processeurs d'annotations Java. Room 3.0 est toujours compatible avec les sources Java en tant qu'entrées.
- Coroutines en premier : les fonctions DAO doivent être des fonctions
suspend, à l'exception des types observables.CoroutineContextremplace les exécutants. - Pas de SupportSQLite : les API
SQLiteDriversont compatibles avec Room. Room supprimeSupportSQLiteDatabasedes API de base. - Modifications de l'API : les migrations et les rappels de base de données utilisent
SQLiteConnectionau lieu deSupportSQLiteDatabase. - Convertisseurs pour les types réactifs : les types renvoyés RxJava, LiveData, Guava et Paging
nécessitent l'enregistrement de
@DaoReturnTypeConverters.
Nous vous recommandons de migrer en deux phases distinctes : préparez et modernisez d'abord votre codebase dans Room 2.x, puis passez à Room 3.0.
Préparer et moderniser dans Room 2.x
Avant de migrer vers Room 3.0, vous pouvez effectuer la plupart des tâches de modernisation en passant à la version actuelle de Room 2.x, comme Room 2.8. Room 2.8 est compatible avec Kotlin Multiplatform (KMP) et inclut de nombreuses API de pilote utilisées par Room 3.0.
Passer à Room 2.8 ou version ultérieure
Mettez à jour votre configuration de compilation pour utiliser la version actuelle de 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" }
Migrer de KAPT vers KSP
Room 3.0 n'est pas compatible avec les processeurs d'annotations Java ni avec KAPT. Vous devez utiliser le traitement des symboles Kotlin (KSP). Vous pouvez effectuer cette transition tout en restant sur Room 2.x.
Dans le fichier
build.gradle.ktsde votre module, appliquez le plug-in KSP :plugins { id("com.google.devtools.ksp") version "<ksp_version>" }Assurez-vous que la version de KSP est compatible avec votre version de Kotlin.
Remplacez
kaptouannotationProcessorparksppour la dépendance du compilateur Room :dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
Adopter les coroutines
Room 3.0 nécessite des coroutines pour les opérations asynchrones.
- Mettez à jour vos DAO : à moins qu'ils ne renvoient un type réactif observable, tel que
Flowou des types RxJava, toutes les fonctions DAO doivent être des fonctionssuspend.
// 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>
}
- Si vous avez configuré votre
RoomDatabaseavec unExecutorpersonnalisé pour effectuer des opérations de base de données, migrez versCoroutineContextà l'aide desetQueryCoroutineContextsur le compilateur :
Room.databaseBuilder<AppDatabase>(context, "db")
.setQueryCoroutineContext(Dispatchers.IO)
.build()
Adopter les API de pilote et éviter Support SQLite
Room 3.0 est entièrement compatible avec SQLiteDriver et n'est plus compatible avec SupportSQLiteDatabase dans ses API de base.
Si vous n'appelez pas setDriver pour définir un SQLiteDriver sur votre compilateur de base de données, Room 2.8 fonctionne dans un mode de compatibilité où les API Support SQLite et Driver fonctionnent toutes les deux. Ce mode de compatibilité vous permet de convertir progressivement votre codebase avant d'activer le pilote.
- Convertir les migrations : migrez vos
MigrationetAutoMigrationSpecsous-classes pour utiliserSQLiteConnectionau lieu deSupportSQLiteDatabase.
// 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"
)
}
}
- Convertir les rappels de base de données : mettez à jour les
RoomDatabase.Callbackimplémentations pour utiliserSQLiteConnection:
// 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) {
// ...
}
}
- Convertir les fonctions DAO
@RawQuery: pour les fonctions annotées avec@RawQuery, utilisezRoomRawQueryau lieu deSupportSQLiteQuery:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
@RawQuery
fun getUser(query: SupportSQLiteQuery): User
}
// After (RoomRawQuery)
@Dao
interface UserDao {
@RawQuery
suspend fun getUser(query: RoomRawQuery): User
}
Vous pouvez construire un RoomRawQuery au moment de l'exécution :
val query = RoomRawQuery(
sql = "SELECT * FROM User WHERE id = ?",
onBindStatement = { statement ->
statement.bindInt(1, userId)
}
)
- Convertir les API de transaction : remplacez les blocs
withTransactionetrunInTransactionpropres à Android parwithWriteTransactionouwithReadTransaction:
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction
db.withWriteTransaction {
// perform database operations
}
Si vous avez besoin d'un accès direct de bas niveau à la connexion de transaction, vous pouvez également utiliser useWriterConnection avec immediateTransaction.
- Éviter l'utilisation directe de
SupportSQLiteDatabase: si vous disposez d'un code hérité étendu qui nécessite toujoursSupportSQLiteDatabaseet que vous ne pouvez pas encore le migrer, utilisez l'artefact de compatibilitéandroidx.room:room-sqlite-wrapper:
dependencies {
implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}
Ensuite, utilisez getSupportWrapper pour obtenir un SupportSQLiteDatabase à partir de votre instance de base de données Room :
import androidx.room.support.getSupportWrapper
val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
- Définir le pilote SQLite : une fois que vous avez migré toutes les utilisations de l'API Room vers les API de pilote, configurez un pilote, tel que
BundledSQLiteDriverouAndroidSQLiteDriver, en appelantsetDriverdans votre compilateurRoomDatabase:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
val db = Room.databaseBuilder<AppDatabase>(context, "db")
.setDriver(BundledSQLiteDriver())
.build()
Adopter le suivi des invalidations basé sur le flux
Room 2.8 introduit l'API InvalidationTracker.createFlow. Utilisez cette API pour migrer des implémentations InvalidationTracker.Observer héritées tout en restant sur Room 2.x. Cela prépare votre codebase pour Room 3.0, qui supprime complètement 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()
}
Migrer vers Room 3.0
Une fois que vous avez modernisé votre application sur Room 2.x, la transition vers Room 3.0 implique la mise à jour des dépendances, des importations de packages et des rappels de base de données.
Mettre à jour les dépendances et les importations de packages
- Dans votre configuration de compilation, remplacez les dépendances
androidx.roomparandroidx.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" }
- Mettez à jour votre bloc de dépendances :
dependencies {
implementation(libs.androidx.room3.runtime)
ksp(libs.androidx.room3.compiler)
}
- Mettez à jour vos importations de packages. Remplacez
import androidx.room.*parimport androidx.room3.*.
Mettre à jour les API de convertisseur de type
Room 3.0 renomme les API de convertisseur de type pour clarifier leur utilisation dans la conversion des valeurs de colonne et éviter toute confusion avec les convertisseurs de type renvoyé DAO.
Mettez à jour les annotations et fonctions suivantes dans votre codebase :
- Renommez
@TypeConverteren@ColumnTypeConverter. - Renommez
@TypeConvertersen@ColumnTypeConverters. - Renommez
@ProvidedTypeConverteren@ProvidedColumnTypeConverter. - Renommez
RoomDatabase.Builder.addTypeConverterenaddColumnTypeConverter.
Exemple :
// 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()
Mettre à jour les rappels pour suspendre les fonctions
Dans Room 3.0, les rappels et les migrations de base de données utilisent SQLiteConnection et sont des fonctions suspend.
- Mettez à jour vos classes
Migrationmanuelles :
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"
)
}
}
- Mettez à jour vos implémentations
RoomDatabase.Callback:
val callback = object : RoomDatabase.Callback() {
override suspend fun onCreate(connection: SQLiteConnection) {
// ...
}
}
Enregistrer les convertisseurs de type renvoyé DAO
Dans Room 3.0, les types renvoyés réactifs, tels que RxJava, LiveData, Guava et Paging, nécessitent l'enregistrement des convertisseurs de type renvoyé DAO à l'aide de @DaoReturnTypeConverters.
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
@Query("SELECT * FROM User")
fun getAllPaginated(): PagingSource<Int, User>
}
- Paging (
PagingSource) : enregistrezPagingSourceDaoReturnTypeConverterà partir de l'artefactandroidx.room3:room3-paging. - RxJava (
Observable,Flowable,Single,Maybe,Completable) : enregistrezRxDaoReturnTypeConvertersà partir de l'artefactandroidx.room3:room3-rxjava3. - Guava (
ListenableFuture) : enregistrezGuavaDaoReturnTypeConverterà partir de l'artefactandroidx.room3:room3-guava. - LiveData (
LiveData) : enregistrezLiveDataDaoReturnTypeConverterà partir de l'androidx.room3:room3-livedataartefact.
Vérifier la suppression de l'observateur InvalidationTracker
Room 3.0 supprime complètement InvalidationTracker.Observer et les méthodes d'enregistrement associées, telles que addObserver et removeObserver.
Si vous n'êtes pas encore passé aux flux de coroutines en
Phase 1, vous devez migrer toutes les utilisations Observer vers
createFlow :
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}