Room 3.0 es una actualización de versión principal que hace que la biblioteca priorice a Kotlin. Es compatible con Kotlin Multiplataforma (KMP), requiere el Procesamiento de Símbolos de Kotlin (KSP) y aplica corrutinas para operaciones asíncronas.
Para evitar problemas de compatibilidad con las apps existentes de Room 2.x y las dependencias transitivas, Room 3.0 reside en un paquete nuevo: androidx.room3.
En esta guía, se describen los pasos necesarios para migrar tu implementación existente de Room 2.x a Room 3.0.
Cambios clave en Room 3.0
Antes de comenzar la migración, familiarízate con las principales diferencias:
- Paquete y artefactos nuevos: Todas las clases residen en
androidx.room3. Los artefactos usan elroom3prefijo, comoandroidx.room3:room3-runtime. - Solo Kotlin y KSP: Room 3.0 no admite la generación de código Java. Usa KSP en lugar de KAPT o procesadores de anotaciones de Java. Room 3.0 aún admite fuentes Java como entradas.
- Prioridad para las corrutinas: Las funciones DAO deben ser funciones
suspendexcepto para los tipos observables.CoroutineContextreemplaza a los ejecutores. - Sin SupportSQLite:
SQLiteDriverLas APIs respaldan Room. Room quitaSupportSQLiteDatabasede las APIs principales. - Cambios en la API: Las migraciones y las devoluciones de llamada de la base de datos usan
SQLiteConnectionen lugar deSupportSQLiteDatabase. - Convertidores para tipos reactivos: Los tipos de datos que se muestran de RxJava, LiveData, Guava y Paging
requieren que registres
@DaoReturnTypeConverters.
Te recomendamos que realices la migración en dos fases distintas: primero, prepara y moderniza tu base de código en Room 2.x y, luego, cambia a Room 3.0.
Prepara y moderniza en Room 2.x
Antes de migrar a Room 3.0, puedes realizar la mayor parte del trabajo de modernización actualizando a la versión actual de Room 2.x, como Room 2.8. Room 2.8 admite Kotlin Multiplataforma, o KMP, e incluye muchas APIs de controlador que usa Room 3.0.
Actualiza a Room 2.8 y versiones posteriores
Actualiza tu configuración de compilación para usar la versión actual 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" }
Migra de KAPT a KSP
Room 3.0 no admite procesadores de anotaciones de Java ni KAPT. Debes usar el Procesamiento de Símbolos de Kotlin (KSP). Puedes realizar esta transición mientras usas Room 2.x.
En el archivo
build.gradle.ktsde tu módulo, aplica el complemento KSP:plugins { id("com.google.devtools.ksp") version "<ksp_version>" }Asegúrate de que la versión de KSP sea compatible con tu versión de Kotlin.
Reemplaza
kaptoannotationProcessorporksppara la dependencia del compilador de Room:dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
Adopta corrutinas
Room 3.0 requiere corrutinas para operaciones asíncronas.
- Actualiza tus DAO: A menos que muestren un tipo reactivo observable, como
Flowo tipos RxJava, todas las funciones DAO deben ser funcionessuspend.
// 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 configuraste tu
RoomDatabasecon unExecutorpersonalizado para realizar operaciones de bases de datos, migra aCoroutineContextconsetQueryCoroutineContexten el compilador:
Room.databaseBuilder<AppDatabase>(context, "db")
.setQueryCoroutineContext(Dispatchers.IO)
.build()
Adopta las APIs de controlador y evita Support SQLite
Room 3.0 está completamente respaldado por SQLiteDriver y ya no admite SupportSQLiteDatabase en sus APIs principales.
Si no llamas a setDriver para establecer un SQLiteDriver en el compilador de tu base de datos, Room 2.8 opera en un modo de compatibilidad en el que funcionan tanto Support SQLite como las APIs de controlador. Este modo de compatibilidad te permite convertir tu base de código de forma incremental antes de habilitar el controlador.
- Convierte migraciones: Migra tus
MigrationyAutoMigrationSpecsubclases para usarSQLiteConnectionen lugar 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"
)
}
}
- Convierte devoluciones de llamada de la base de datos: Actualiza las implementaciones de
RoomDatabase.Callbackpara usarSQLiteConnection:
// 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) {
// ...
}
}
- Convierte funciones DAO
@RawQuery: Para las funciones anotadas con@RawQuery, usaRoomRawQueryen lugar deSupportSQLiteQuery:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
@RawQuery
fun getUser(query: SupportSQLiteQuery): User
}
// After (RoomRawQuery)
@Dao
interface UserDao {
@RawQuery
suspend fun getUser(query: RoomRawQuery): User
}
Puedes construir un RoomRawQuery en el tiempo de ejecución:
val query = RoomRawQuery(
sql = "SELECT * FROM User WHERE id = ?",
onBindStatement = { statement ->
statement.bindInt(1, userId)
}
)
- Convierte APIs de transacción: Reemplaza los bloques
withTransactionyrunInTransactionsolo para Android porwithWriteTransactionowithReadTransaction:
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction
db.withWriteTransaction {
// perform database operations
}
Si necesitas acceso directo de bajo nivel a la conexión de transacción, también puedes usar useWriterConnection con immediateTransaction.
- Evita el uso directo de
SupportSQLiteDatabase: Si tienes un código heredado extenso que aún requiereSupportSQLiteDatabasey aún no puedes migrarlo, usa el artefacto de compatibilidadandroidx.room:room-sqlite-wrapper:
dependencies {
implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}
Luego, usa getSupportWrapper para obtener un SupportSQLiteDatabase de tu instancia de base de datos de Room:
import androidx.room.support.getSupportWrapper
val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
- Establece el controlador de SQLite: Después de migrar todos los usos de la API de Room a las APIs de controlador, configura un controlador, como
BundledSQLiteDriveroAndroidSQLiteDriver, llamando asetDriveren tu compiladorRoomDatabase:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
val db = Room.databaseBuilder<AppDatabase>(context, "db")
.setDriver(BundledSQLiteDriver())
.build()
Adopta el seguimiento de invalidación basado en flujo
Room 2.8 presenta la API de InvalidationTracker.createFlow. Usa esta API para migrar de las implementaciones heredadas de InvalidationTracker.Observer mientras usas Room 2.x. Esto prepara tu base de código para Room 3.0, que quita por completo 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()
}
Migra a Room 3.0
Una vez que modernices tu aplicación en Room 2.x, la transición a Room 3.0 implicará actualizar las dependencias, las importaciones de paquetes y las devoluciones de llamada de la base de datos.
Actualiza las dependencias y las importaciones de paquetes
- En tu configuración de compilación, reemplaza las dependencias de
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" }
- Actualiza tu bloque de dependencias:
dependencies {
implementation(libs.androidx.room3.runtime)
ksp(libs.androidx.room3.compiler)
}
- Actualiza las importaciones de paquetes. Reemplaza
import androidx.room.*porimport androidx.room3.*.
Actualiza las APIs del convertidor de tipos
Room 3.0 cambia el nombre de las APIs del convertidor de tipos para aclarar su uso para convertir valores de columna y evitar confusiones con los convertidores de tipos de datos que se muestran de DAO.
Actualiza las siguientes anotaciones y funciones en tu base de código:
- Cambia el nombre de
@TypeConvertera@ColumnTypeConverter. - Cambia el nombre de
@TypeConvertersa@ColumnTypeConverters. - Cambia el nombre de
@ProvidedTypeConvertera@ProvidedColumnTypeConverter. - Cambia el nombre de
RoomDatabase.Builder.addTypeConverteraaddColumnTypeConverter.
Ejemplo:
// 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()
Actualiza las devoluciones de llamada para suspender funciones
En Room 3.0, las devoluciones de llamada y las migraciones de la base de datos usan SQLiteConnection y son funciones suspend.
- Actualiza tus clases
Migrationmanuales:
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"
)
}
}
- Actualiza tus implementaciones de
RoomDatabase.Callback:
val callback = object : RoomDatabase.Callback() {
override suspend fun onCreate(connection: SQLiteConnection) {
// ...
}
}
Registra los convertidores de tipos de datos que se devuelve de DAO
En Room 3.0, los tipos de datos que se devuelven reactivos, como RxJava, LiveData, Guava y Paging, requieren que registres los convertidores de tipos de datos que se devuelven de DAO con @DaoReturnTypeConverters.
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
@Query("SELECT * FROM User")
fun getAllPaginated(): PagingSource<Int, User>
}
- Paging (
PagingSource): RegistraPagingSourceDaoReturnTypeConverterdesde el artefactoandroidx.room3:room3-paging. - RxJava (
Observable,Flowable,Single,Maybe,Completable): RegistraRxDaoReturnTypeConvertersdesde el artefactoandroidx.room3:room3-rxjava3. - Guava (
ListenableFuture): RegistraGuavaDaoReturnTypeConverterdesde el artefactoandroidx.room3:room3-guava. - LiveData (
LiveData): RegistraLiveDataDaoReturnTypeConverterdesde elandroidx.room3:room3-livedataartefacto.
Verifica la eliminación del observador InvalidationTracker
Room 3.0 quita por completo InvalidationTracker.Observer y los métodos de registro relacionados, como addObserver y removeObserver.
Si aún no hiciste la transición a los flujos de corrutinas en
la Fase 1, debes migrar todos los usos de Observer a
createFlow:
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}