La biblioteca de persistencias de Room brinda una capa de abstracción para SQLite que permite acceder a la base de datos sin problemas y, al mismo tiempo, aprovechar toda la potencia de SQLite. Esta página se enfoca en el uso de Room en proyectos multiplataforma de Kotlin (KMP). Para obtener más información sobre el uso de Room, consulta Cómo guardar contenido en una base de datos local con Room o nuestros ejemplos oficiales.
Configura las dependencias
La versión actual de Room que admite KMP es 2.7.0-alpha01 o versiones posteriores.
Para configurar Room en tu proyecto de KMP, agrega las dependencias de los artefactos en el archivo build.gradle.kts
de tu módulo:
androidx.room:room-gradle-plugin
: Es el complemento de Gradle para configurar esquemas de Room.androidx.room:room-compiler
: Es el procesador de KSP que genera código.androidx.room:room-runtime
: Es la parte del entorno de ejecución de la biblioteca.androidx.sqlite:sqlite-bundled
: (Opcional) La biblioteca de SQLite incluida
Además, debes configurar el controlador SQLite de Room. Estos controladores difieren según la plataforma de destino. Consulta Implementaciones de controladores para obtener descripciones de las implementaciones de controladores disponibles.
Para obtener más información sobre la configuración, consulta lo siguiente:
- Establece la ubicación del esquema con el complemento de Gradle de Room.
- KSP con Kotlin multiplataforma
- Agregar dependencias de tiempo de ejecución
Define las clases de la base de datos
Debes crear una clase de base de datos con anotaciones @Database
junto con DAOs y entidades dentro del conjunto de fuentes común de tu módulo KMP compartido. Colocar estas clases en fuentes comunes permitirá que se compartan en todas las plataformas de destino.
Cuando declaras un objeto expect
con la interfaz RoomDatabaseConstructor
, el compilador de Room genera las implementaciones de actual
. Es posible que Android Studio emita una advertencia "Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
. Puedes suprimir la advertencia con @Suppress("NO_ACTUAL_FOR_EXPECT")
.
// shared/src/commonMain/kotlin/Database.kt
@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): TodoDao
}
// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
@Dao
interface TodoDao {
@Insert
suspend fun insert(item: TodoEntity)
@Query("SELECT count(*) FROM TodoEntity")
suspend fun count(): Int
@Query("SELECT * FROM TodoEntity")
fun getAllAsFlow(): Flow<List<TodoEntity>>
}
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
Ten en cuenta que, de manera opcional, puedes usar declaraciones reales o esperadas para crear implementaciones de Room específicas de la plataforma. Por ejemplo, puedes agregar una DAO específica de la plataforma que se define en código común con expect
y, luego, especificar las definiciones de actual
con consultas adicionales en conjuntos de orígenes específicos de la plataforma.
Cómo crear el compilador de bases de datos
Debes definir un compilador de bases de datos para crear instancias de Room en cada plataforma. Esta es la única parte de la API que debe estar en conjuntos de orígenes específicos de la plataforma debido a las diferencias en las APIs del sistema de archivos. Por ejemplo, en Android, la ubicación de la base de datos suele obtenerse a través de la API de Context.getDatabasePath()
, mientras que, en iOS, la ubicación de la base de datos se obtiene con NSFileManager
.
Android
Para crear la instancia de base de datos, especifica un Contexto junto con la ruta de acceso de la base de datos.
// shared/src/androidMain/kotlin/Database.kt
fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = ctx.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
Para crear la instancia de la base de datos, proporciona una ruta de acceso a la base de datos con NSFileManager
, que suele estar en NSDocumentDirectory
.
// shared/src/iosMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFilePath = documentDirectory() + "/my_room.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFilePath,
)
}
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}
JVM (computadora de escritorio)
Para crear la instancia de base de datos, proporciona una ruta de acceso a la base de datos con las APIs de Java o Kotlin.
// shared/src/jvmMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
Reducción y ofuscación
Si el proyecto está reducido o ofuscado, se debe incluir la siguiente regla de Proguard para que Room pueda encontrar la implementación generada de la definición de la base de datos:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Creación de instancias de la base de datos
Una vez que obtengas el RoomDatabase.Builder
de uno de los constructores específicos de la plataforma, puedes configurar el resto de la base de datos de Room en código común junto con la creación de instancias de la base de datos real.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.addMigrations(MIGRATIONS)
.fallbackToDestructiveMigrationOnDowngrade()
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
Selecciona un SQLiteDriver
Los fragmentos de código anteriores usan BundledSQLiteDriver
. Este es el
controlador recomendado que incluye SQLite compilado desde la fuente, que proporciona la
versión más coherente y actualizada de SQLite en todas las plataformas. Si deseas usar el SQLite proporcionado por el SO, usa la API de setDriver
en conjuntos de orígenes específicos de la plataforma que especifiquen un controlador específico de la plataforma. En Android, puedes usar AndroidSQLiteDriver
, mientras que en iOS puedes usar NativeSQLiteDriver
. Para usar NativeSQLiteDriver
, debes proporcionar una opción de vinculador para que la app para iOS se vincule de forma dinámica con el SQLite del sistema.
// shared/build.gradle.kts
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "TodoApp"
isStatic = true
// Required when using NativeSQLiteDriver
linkerOpts.add("-lsqlite3")
}
}
}
Diferencias
Room se desarrolló originalmente como una biblioteca de Android y, más tarde, se migró a KMP con un enfoque en la compatibilidad con la API. La versión de KMP de Room difiere un poco entre las plataformas y de la versión específica de Android. Estas diferencias se enumeran y describen a continuación.
Cómo bloquear funciones de DAO
Cuando usas Room para KMP, todas las funciones de DAO compiladas para plataformas que no son de Android deben ser funciones suspend
, a excepción de los tipos de datos que se muestran reactivos, como Flow
.
// shared/src/commonMain/kotlin/MultiplatformDao.kt
@Dao
interface MultiplatformDao {
// ERROR: Blocking function not valid for non-Android targets
@Query("SELECT * FROM Entity")
fun blockingQuery(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
suspend fun query(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
fun queryFlow(): Flow<List<Entity>>
// ERROR: Blocking function not valid for non-Android targets
@Transaction
fun blockingTransaction() { // … }
// OK
@Transaction
suspend fun transaction() { // … }
}
Room se beneficia de la biblioteca kotlinx.coroutines
asíncrona y repleta de funciones que ofrece Kotlin para varias plataformas. Para obtener una funcionalidad óptima, las funciones suspend
se aplican para las DAO compiladas en un proyecto de KMP, a excepción de las
DAO específicas de Android para mantener la retrocompatibilidad con la base de código
existente.
Diferencias entre las funciones de KMP
En esta sección, se describe cómo difieren las funciones entre las versiones de Room de KMP y de la plataforma de Android.
Funciones de DAO de @RawQuery
Las funciones con anotaciones @RawQuery
que se compilan para plataformas que no son de Android deberán declarar un parámetro de tipo RoomRawQuery
en lugar de SupportSQLiteQuery
.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
Luego, se puede usar un RoomRawQuery
para crear una consulta en el tiempo de ejecución:
suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?"
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todosDao.getTodos(query)
}
Devolución de llamada de consulta
Las siguientes APIs para configurar devoluciones de llamada de consulta no están disponibles en común y, por lo tanto, no están disponibles en plataformas que no sean Android.
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
Tenemos la intención de agregar compatibilidad con la devolución de llamada de consulta en una versión futura de Room.
La API para configurar un RoomDatabase
con una devolución de llamada de consulta RoomDatabase.Builder.setQueryCallback
junto con la interfaz de devolución de llamada RoomDatabase.QueryCallback
no están disponibles en común y, por lo tanto, no están disponibles en otras plataformas que no sean Android.
Base de datos con cierre automático
La API para habilitar el cierre automático después de un tiempo de espera, RoomDatabase.Builder.setAutoCloseTimeout
, solo está disponible en Android y no en otras plataformas.
Base de datos precompilada
Las siguientes APIs para crear un RoomDatabase
con una base de datos existente (es decir, una base de datos empaquetada previamente) no están disponibles en común y, por lo tanto, no están disponibles en otras plataformas que no sean Android. Estas APIs son las siguientes:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
Tenemos la intención de agregar compatibilidad con bases de datos empaquetadas previamente en una versión futura de Room.
Invalidación de instancias múltiples
La API para habilitar la invalidación de varias instancias, RoomDatabase.Builder.enableMultiInstanceInvalidation
, solo está disponible en Android y no en otras plataformas comunes.