Room (multiplataforma de Kotlin)

La biblioteca de persistencias Room brinda una capa de abstracción para SQLite que permite para obtener un acceso más sólido a la base de datos y, al mismo tiempo, aprovechar toda la potencia de SQLite. Esta se centra en el uso de Room en proyectos multiplataforma de Kotlin (KMP). Para ver más Para obtener información sobre el uso de Room, consulta Cómo guardar contenido en una base de datos local con Room. o nuestras muestras oficiales.

Configura dependencias

La versión actual de Room que admite KMP es 2.7.0-alpha01 o una versión posterior

Para configurar Room en tu proyecto de KMP, agrega las dependencias de los artefactos en el build.gradle.kts para 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 KSP que genera código.
  • androidx.room:room-runtime: Es la parte del entorno de ejecución de la biblioteca.
  • androidx.sqlite:sqlite-bundled: Es la biblioteca SQLite empaquetada (opcional).

Además, debes configurar el controlador SQLite de Room. Estos controladores difieren según la plataforma seleccionada. Consulta Implementaciones de controladores para obtener descripciones de las implementaciones de controladores disponibles.

Para obtener información adicional sobre la configuración, consulta lo siguiente:

Define las clases de la base de datos

Debes crear una clase de base de datos anotada con @Database junto con DAOs. y entidades dentro del conjunto común de orígenes de tu módulo KMP compartido. Ubicación estas clases en fuentes comunes permitirán que se compartan entre todos los destinos y plataformas de Google Cloud.

Cuando declaras un objeto expect con la interfaz RoomDatabaseConstructor, el compilador de Room genera el actual. de Google Cloud. Android Studio podría emitir 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 y previstas. para crear implementaciones de Room específicas de la plataforma. Por ejemplo, puedes agregar una DAO específico de la plataforma que se define en código común con expect y, luego, Especifica las definiciones de actual con consultas adicionales en recursos específicos de la plataforma conjuntos de orígenes.

Crea el compilador de bases de datos

Debes definir un compilador de bases de datos para crear una instancia de Room en cada plataforma. Esta es la única parte de la API que debe estar en una fuente específica de la plataforma debido a las diferencias en las APIs de los sistemas de archivos. Por ejemplo, en Android, la ubicación de la base de datos generalmente se obtiene Context.getDatabasePath(), mientras que, en iOS, la ubicación de la base de datos es obtenerse usando NSFileManager.

Android

Para crear la instancia de la base de datos, especifica un Contexto junto con la base de datos. ruta de acceso.

// 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 la base de datos con la NSFileManager, generalmente ubicado 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 (computadoras de escritorio)

Para crear la instancia de la base de datos, proporciona una ruta de la base de datos con Java o Kotlin APIs

// 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,
    )
}

Creación de instancias de la base de datos

Una vez que obtengas el RoomDatabase.Builder de uno de los canales constructores, puedes configurar el resto de la base de datos de Room en código común junto con la creación de instancias reales de la base de datos.

// shared/src/commonMain/kotlin/Database.kt

fun getRoomDatabase(
    builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
  return builder
      .addMigrations(MIGRATIONS)
      .fallbackToDestructiveMigrationOnDowngrade()
      .setDriver(BundledSQLiteDriver())
      .setQueryCoroutineContext(Dispatchers.IO)
      .build()
}

Cómo seleccionar un SQLiteDriver

Los fragmentos de código anteriores usan BundledSQLiteDriver. Este es el controlador recomendado que incluye SQLite compilado desde la fuente, lo que brinda la más coherente y actualizada de SQLite en todas las plataformas. Si Si deseas usar el SQLite que proporciona el SO, usa la API de setDriver en las operaciones Conjuntos de orígenes que especifican un controlador específico de la plataforma Para Android, puedes usar AndroidSQLiteDriver, mientras que para iOS, puedes usar NativeSQLiteDriver. Para usa NativeSQLiteDriver, debes proporcionar una opción de vinculador para que la biblioteca de iOS se vincula dinámicamente con el sistema SQLite.

// 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 luego se migró a KMP con un enfoque en la compatibilidad de APIs. La versión de KMP de Room difiere un poco entre plataformas y de la versión específica de Android. Estas diferencias son que se enumera y describe de la siguiente manera.

Cómo bloquear funciones DAO

Cuando se usa Room para KMP, todas las funciones DAO se compilan para plataformas que no son de Android deben ser funciones suspend, excepto los tipos de datos que se muestran reactivos, como 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 con muchas funciones que ofrece Kotlin para varias plataformas. Para obtener una funcionalidad óptima, suspend se aplican para los DAOs compilados en un proyecto KMP, a excepción de DAO específicos de Android para mantener la retrocompatibilidad con los DAOs existentes base de código.

Diferencias entre funciones con KMP

En esta sección, se describe cómo difieren las funciones entre KMP y la plataforma de Android de Room.

Funciones DAO @RawQuery

Funciones anotadas con @RawQuery que se compilan para plataformas que no son de Android deberá declarar un parámetro de tipo RoomRawQuery en lugar de SupportSQLiteQuery

@Dao
interface TodoDao {
  @RawQuery
  suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}

Se puede usar un RoomRawQuery para crear una consulta en el entorno 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 consultas no están disponibles en común y, por lo tanto, no están disponibles en plataformas distintas de Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Planeamos agregar compatibilidad con la devolución de llamada de consultas 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 distintas de Android.

Cierre de base de datos 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 es no está disponible en otras plataformas.

Base de datos previa al empaquetado

Las siguientes APIs permiten crear un RoomDatabase usando 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 en otras plataformas distintas de Android. Estas son las APIs:

  • 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 Habitación.

Invalidación de varias instancias

La API para habilitar la invalidación de instancias múltiples RoomDatabase.Builder.enableMultiInstanceInvalidation solo está disponible en Android y no está disponible en las plataformas comunes ni en otras plataformas.