Room 3.0 è un aggiornamento della versione principale che porta la libreria a essere Kotlin-first. Supporta Kotlin Multiplatform (KMP), richiede Kotlin Symbol Processing (KSP) e applica le coroutine per le operazioni asincrone.
Per evitare problemi di compatibilità con le app Room 2.x esistenti e le dipendenze transitive, Room 3.0 si trova in un nuovo pacchetto: androidx.room3.
Questa guida illustra i passaggi necessari per eseguire la migrazione dell'implementazione Room 2.x esistente a Room 3.0.
Modifiche principali in Room 3.0
Prima di iniziare la migrazione, prendi confidenza con le principali differenze:
- Nuovo pacchetto e nuovi artefatti: tutte le classi si trovano in
androidx.room3. Gli artefatti utilizzano il prefissoroom3, ad esempioandroidx.room3:room3-runtime. - Solo Kotlin e KSP: Room 3.0 non supporta la generazione di codice Java. Utilizza KSP anziché KAPT o i processori di annotazione Java. Room 3.0 supporta ancora le origini Java come input.
- Coroutines first: le funzioni DAO devono essere funzioni
suspend, ad eccezione dei tipi osservabili.CoroutineContextsostituisce gli esecutori. - Nessun SupportSQLite: le API supportano Room.
SQLiteDriverRoom rimuoveSupportSQLiteDatabasedalle API principali. - Modifiche alle API: le migrazioni e i callback del database utilizzano
SQLiteConnectionanzichéSupportSQLiteDatabase. - Convertitori per tipi reattivi: i tipi restituiti RxJava, LiveData, Guava e Paging
richiedono la registrazione di
@DaoReturnTypeConverters.
Ti consigliamo di eseguire la migrazione in due fasi distinte: prima prepara e modernizza il codebase in Room 2.x, poi passa a Room 3.0.
Preparazione e modernizzazione in Room 2.x
Prima di eseguire la migrazione a Room 3.0, puoi eseguire la maggior parte del lavoro di modernizzazione aggiornando alla versione corrente di Room 2.x, ad esempio Room 2.8. Room 2.8 supporta Kotlin Multiplatform, o KMP, e include molte API driver utilizzate da Room 3.0.
Esegui l'aggiornamento a Room 2.8 e versioni successive
Aggiorna la configurazione di compilazione per utilizzare la versione corrente di 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" }
Esegui la migrazione da KAPT a KSP
Room 3.0 non supporta i processori di annotazione Java o KAPT. Devi utilizzare Kotlin Symbol Processing (KSP). Puoi eseguire questa transizione mentre utilizzi ancora Room 2.x.
In
build.gradle.ktsdel modulo, applica il plug-in KSP:plugins { id("com.google.devtools.ksp") version "<ksp_version>" }Assicurati che la versione KSP sia compatibile con la tua versione Kotlin.
Sostituisci
kaptoannotationProcessorconkspper la dipendenza del compilatore Room:dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
Adotta le coroutine
Room 3.0 richiede le coroutine per le operazioni asincrone.
- Aggiorna i DAO: a meno che non restituiscano un tipo reattivo osservabile, come i tipi
Flowo RxJava, tutte le funzioni DAO devono essere funzionisuspend.
// 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>
}
- Se hai configurato
RoomDatabasecon unExecutorpersonalizzato per eseguire le operazioni del database, esegui la migrazione aCoroutineContextutilizzandosetQueryCoroutineContextnel builder:
Room.databaseBuilder<AppDatabase>(context, "db")
.setQueryCoroutineContext(Dispatchers.IO)
.build()
Adotta le API driver ed evita Support SQLite
Room 3.0 è completamente supportato da SQLiteDriver e non supporta più SupportSQLiteDatabase nelle sue API principali.
Se non chiami setDriver per impostare un SQLiteDriver nel builder del database, Room 2.8 funziona in modalità di compatibilità in cui funzionano sia Support SQLite sia le API driver. Questa modalità di compatibilità ti consente di convertire gradualmente il codebase prima di abilitare il driver.
- Converti le migrazioni: esegui la migrazione delle sottoclassi
MigrationeAutoMigrationSpecper utilizzareSQLiteConnectionanziché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"
)
}
}
- Converti i callback del database: aggiorna le implementazioni
RoomDatabase.Callbackper utilizzareSQLiteConnection:
// 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) {
// ...
}
}
- Converti le funzioni DAO
@RawQuery: per le funzioni annotate con@RawQuery, utilizzaRoomRawQueryanzichéSupportSQLiteQuery:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
@RawQuery
fun getUser(query: SupportSQLiteQuery): User
}
// After (RoomRawQuery)
@Dao
interface UserDao {
@RawQuery
suspend fun getUser(query: RoomRawQuery): User
}
Puoi creare un RoomRawQuery in fase di runtime:
val query = RoomRawQuery(
sql = "SELECT * FROM User WHERE id = ?",
onBindStatement = { statement ->
statement.bindInt(1, userId)
}
)
- Converti le API delle transazioni: sostituisci i blocchi solo per Android
withTransactionerunInTransactionconwithWriteTransactionowithReadTransaction:
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction
db.withWriteTransaction {
// perform database operations
}
Se hai bisogno di un accesso diretto di basso livello alla connessione della transazione, puoi anche utilizzare useWriterConnection con immediateTransaction.
- Evita l'utilizzo diretto di
SupportSQLiteDatabase: se hai un codice legacy esteso che richiede ancoraSupportSQLiteDatabasee non puoi ancora eseguirne la migrazione, utilizza l'artefatto di compatibilitàandroidx.room:room-sqlite-wrapper:
dependencies {
implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}
Poi, utilizza getSupportWrapper per ottenere un SupportSQLiteDatabase dall'istanza del database Room:
import androidx.room.support.getSupportWrapper
val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
- Imposta il driver SQLite: dopo aver eseguito la migrazione di tutti gli utilizzi dell'API Room alle API driver, configura un driver, ad esempio
BundledSQLiteDriveroAndroidSQLiteDriver, chiamandosetDrivernel builderRoomDatabase:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
val db = Room.databaseBuilder<AppDatabase>(context, "db")
.setDriver(BundledSQLiteDriver())
.build()
Adotta il monitoraggio delle invalidazioni basato su flussi
Room 2.8 introduce l'API InvalidationTracker.createFlow. Utilizza questa API per eseguire la migrazione dalle implementazioni InvalidationTracker.Observer legacy mentre utilizzi ancora Room 2.x. In questo modo, il codebase viene preparato per Room 3.0, che rimuove completamente 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()
}
Esegui la migrazione a Room 3.0
Una volta modernizzata l'applicazione in Room 2.x, la transizione a Room 3.0 comporta l'aggiornamento delle dipendenze, delle importazioni dei pacchetti e dei callback del database.
Aggiorna le dipendenze e le importazioni dei pacchetti
- Nella configurazione di compilazione, sostituisci le dipendenze
androidx.roomconandroidx.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" }
- Aggiorna il blocco delle dipendenze:
dependencies {
implementation(libs.androidx.room3.runtime)
ksp(libs.androidx.room3.compiler)
}
- Aggiorna le importazioni dei pacchetti. Sostituisci
import androidx.room.*conimport androidx.room3.*.
Aggiorna le API del convertitore di tipi
Room 3.0 rinomina le API del convertitore di tipi per chiarirne l'utilizzo per la conversione dei valori delle colonne ed evitare confusione con i convertitori di tipi restituiti DAO.
Aggiorna le seguenti annotazioni e funzioni nel codebase:
- Rinomina
@TypeConverterin@ColumnTypeConverter. - Rinomina
@TypeConvertersin@ColumnTypeConverters. - Rinomina
@ProvidedTypeConverterin@ProvidedColumnTypeConverter. - Rinomina
RoomDatabase.Builder.addTypeConverterinaddColumnTypeConverter.
Esempio:
// 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()
Aggiorna i callback alle funzioni di sospensione
In Room 3.0, i callback e le migrazioni del database utilizzano SQLiteConnection e sono funzioni suspend.
- Aggiorna le classi
Migrationmanuali:
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"
)
}
}
- Aggiorna le implementazioni
RoomDatabase.Callback:
val callback = object : RoomDatabase.Callback() {
override suspend fun onCreate(connection: SQLiteConnection) {
// ...
}
}
Registra i convertitori di tipi restituiti DAO
In Room 3.0, i tipi restituiti reattivi, come RxJava, LiveData, Guava e Paging, richiedono la registrazione dei convertitori di tipi restituiti DAO utilizzando @DaoReturnTypeConverters.
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
@Query("SELECT * FROM User")
fun getAllPaginated(): PagingSource<Int, User>
}
- Paging (
PagingSource): registraPagingSourceDaoReturnTypeConverterdall'artefattoandroidx.room3:room3-paging. - RxJava (
Observable,Flowable,Single,Maybe,Completable): registraRxDaoReturnTypeConvertersdall'artefattoandroidx.room3:room3-rxjava3. - Guava (
ListenableFuture): registraGuavaDaoReturnTypeConverterdall'artefattoandroidx.room3:room3-guava. - LiveData (
LiveData): registraLiveDataDaoReturnTypeConverterdall'androidx.room3:room3-livedataartefatto.
Verifica la rimozione di InvalidationTracker Observer
Room 3.0 rimuove completamente InvalidationTracker.Observer e i metodi di registrazione correlati, come addObserver e removeObserver.
Se non hai ancora eseguito la transizione ai flussi di coroutine in
Fase 1, devi eseguire la migrazione di tutti gli utilizzi di Observer a
createFlow:
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}