Room (Kotlin multiplataforma)

A biblioteca de persistência Room oferece uma camada de abstração sobre o SQLite para permitir para um acesso mais robusto ao banco de dados, aproveitando toda a capacidade do SQLite. Isso foca no uso do Room em projetos Kotlin Multiplatform (KMP). Para mais informações sobre como usar o Room, consulte Salvar dados em um banco de dados local usando o Room. ou nossos exemplos oficiais.

Como configurar dependências

A versão atual do Room compatível com KMP é 2.7.0-alpha01 ou mais recente

Para configurar o Room no seu projeto KMP, adicione as dependências dos artefatos na build.gradle.kts para seu módulo:

  • androidx.room:room-gradle-plugin: plug-in do Gradle para configurar esquemas do Room.
  • androidx.room:room-compiler: o processador KSP que gera o código.
  • androidx.room:room-runtime: a parte do ambiente de execução da biblioteca
  • androidx.sqlite:sqlite-bundled (opcional): biblioteca SQLite agrupada.

Além disso, você precisa configurar o driver SQLite do Room. Esses motoristas diferem com base na plataforma de destino. Consulte Implementações de drivers para ver descrições das implementações de driver disponíveis.

Para mais informações sobre configuração, consulte:

.

Como definir as classes de banco de dados

É necessário criar uma classe de banco de dados com a anotação @Database e DAOs. e entidades dentro do conjunto de origem comum do seu módulo KMP compartilhado. Colocando essas classes em origens comuns permitem que elas sejam compartilhadas plataformas.

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

@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

expect object AppDatabaseConstructor : RoomDatabaseConstructor<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
)

É possível usar declarações reais / esperadas para criar implementações do Room específicas da plataforma. Por exemplo, é possível adicionar um DAO específico da plataforma, definido em código comum, usando expect e, em seguida, especifique as definições de actual com consultas adicionais em consultas específicas dos conjuntos de origem.

Como criar o builder do banco de dados

Você precisa definir um builder de banco de dados para instanciar o Room em cada plataforma. Isso é a única parte da API que precisa estar na origem específica da plataforma devido às diferenças nas APIs dos sistemas de arquivos. Por exemplo, no Android, o local do banco de dados geralmente é obtido pelo API Context.getDatabasePath(). No iOS, o local do banco de dados é ser obtidos usando NSFileManager.

Android

Para criar a instância do banco de dados, especifique um Contexto junto com o banco de dados caminho.

// 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 criar a instância do banco de dados, forneça um caminho de banco de dados usando o NSFileManager, geralmente localizado em 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 (computador)

Para criar a instância do banco de dados, forneça um caminho de banco de dados usando Java ou Kotlin APIs de terceiros.

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

Instanciação de banco de dados

Quando você receber o RoomDatabase.Builder de um dos métodos é possível configurar o restante do banco de dados do Room em um código comum além da instanciação real do banco de dados.

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

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

Como selecionar um SQLiteDriver

Os snippets de código anteriores usam BundledSQLiteDriver. Esta é a o driver recomendado que inclui SQLite compilado da origem, que fornece a mais consistente e atualizada do SQLite em todas as plataformas. Se você quiser usar o SQLite fornecido pelo SO, use a API setDriver em APIs específicas conjuntos de origem que especificam um driver específico da plataforma. Para Android, você pode usar AndroidSQLiteDriver, enquanto para iOS é possível usar NativeSQLiteDriver. Para usar NativeSQLiteDriver, será necessário fornecer uma opção do vinculador para que o iOS o app é vinculado dinamicamente ao SQLite do 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")
        }
    }
}

Diferenças

Originalmente, o Room foi desenvolvido como uma biblioteca Android e depois migrou para KMP com foco na compatibilidade de APIs. A versão KMP do Room é um pouco diferente entre plataformas e da versão específica do Android. Essas diferenças são listados e descritos a seguir.

Como bloquear funções DAO

Ao usar o Room para KMP, todas as funções DAO compiladas para plataformas que não são Android, precisam ser funções suspend, com exceção de tipos de retorno reativos, 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() { // … }
}

O Room se beneficia da biblioteca kotlinx.coroutines assíncrona, que é repleta de recursos que o Kotlin oferece para várias plataformas. Para ter a melhor funcionalidade, suspend são aplicadas a DAOs compilados em um projeto KMP, com exceção de DAOs específicos do Android para manter a compatibilidade com versões anteriores do elemento atual; de código aberto.

Diferenças de atributos com o KMP

Esta seção descreve as diferenças entre os recursos do KMP e da plataforma Android do Room.

Funções DAO do @RawQuery

Funções anotadas com @RawQuery que são compiladas para plataformas que não são Android precisará declarar um parâmetro do tipo RoomRawQuery em vez de SupportSQLiteQuery

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

Um RoomRawQuery pode ser usado para criar uma consulta no momento da execução:

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

Callback de consulta

As seguintes APIs para configurar callbacks de consulta não estão disponíveis em comum e, portanto, não estão disponíveis em plataformas que não sejam o Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Pretendemos adicionar suporte a callback de consulta em uma versão futura do Room.

A API para configurar um RoomDatabase com um callback de consulta. RoomDatabase.Builder.setQueryCallback junto com a interface de callback RoomDatabase.QueryCallback não estão em comum e, portanto, não estão disponíveis em outras plataformas que não o Android.

Banco de dados de fechamento automático

a API para ativar o fechamento automático após um tempo limite; RoomDatabase.Builder.setAutoCloseTimeout, está disponível apenas no Android e é e não está disponível em outras plataformas.

Pré-empacotar banco de dados

As seguintes APIs para criar um RoomDatabase usando um banco de dados existente (ou seja, um pré-empacotado) não estão disponíveis em comum e, portanto, não estão disponíveis em outras plataformas que não o Android. Essas APIs são:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

Pretendemos adicionar suporte para bancos de dados pré-empacotados em uma versão futura do Sala

Invalidação de várias instâncias

A API para ativar a invalidação de várias instâncias, RoomDatabase.Builder.enableMultiInstanceInvalidation está disponível apenas nestes dispositivos Android e não está disponível em plataformas comuns ou em outras.