Room (Kotlin multiplataforma)

A biblioteca de persistência Room oferece uma camada de abstração sobre o SQLite para permitir um acesso mais robusto ao banco de dados, aproveitando toda a capacidade do SQLite. Esta página se concentra 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 (links em inglês).

Como configurar dependências

A versão atual do Room com suporte ao KMP é 2.7.0-alpha01 ou mais recente.

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

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

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

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

Como definir as classes do banco de dados

Você precisa criar uma classe de banco de dados anotada com @Database, além de DAOs e entidades dentro do conjunto de origem comum do módulo KMP compartilhado. Colocar essas classes em fontes comuns permite que elas sejam compartilhadas em todas as plataformas de destino.

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

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

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

Você pode 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 que é definido no código comum usando expect e, em seguida, especificar as definições de actual com outras consultas em conjuntos de origem específicos da plataforma.

Como criar o builder de banco de dados

É necessário definir um builder de banco de dados para instanciar o Room em cada plataforma. Essa é a única parte da API que precisa estar em conjuntos de origem específicos da plataforma devido às diferenças nas APIs do sistema de arquivos. Por exemplo, no Android, o local do banco de dados geralmente é recebido pela API Context.getDatabasePath(). Já no iOS, ele é recebido usando NSHomeDirectory.

Android

Para criar a instância do banco de dados, especifique um contexto com o caminho do banco de dados. Não é preciso especificar uma fábrica de banco de dados.

// 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 uma fábrica de banco de dados com o caminho do banco de dados. A fábrica do banco de dados é uma função lambda que invoca uma função de extensão gerada com o nome instantiateImpl com um receptor do tipo KClass<T>, em que T é o tipo da classe com anotação @Database.

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM (computador)

Para criar a instância do banco de dados, especifique apenas o caminho do banco de dados. Você não precisa fornecer uma fábrica de banco de dados.

// shared/src/commonMain/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 do banco de dados

Depois de extrair o RoomDatabase.Builder de um dos construtores específicos da plataforma, é possível configurar o restante do banco de dados do Room em código comum com a 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 o BundledSQLiteDriver. Esse é o driver recomendado para incluir o SQLite compilado na origem, que oferece a versão mais consistente e atualizada do SQLite em todas as plataformas. Se você quiser usar o SQLite fornecido pelo SO, use a API setDriver em conjuntos de origem específicos da plataforma que especificam um driver específico. Para Android, você pode usar AndroidSQLiteDriver e, para iOS, pode usar NativeSQLiteDriver. Para usar o NativeSQLiteDriver, é necessário fornecer uma opção de vinculador para que o app iOS seja 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

O Room foi originalmente desenvolvido como uma biblioteca Android e depois foi migrado para KMP, com foco na compatibilidade com APIs. A versão KMP do Room é um pouco diferente entre as plataformas e a versão específica para Android. Essas diferenças são listadas e descritas da seguinte maneira.

Como bloquear funções DAO

Ao usar o Room para KMP, todas as funções DAO compiladas para plataformas não Android precisam ser funções suspend, exceto os tipos de retorno reativos, 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 e repleta de recursos que o Kotlin oferece para várias plataformas. Para a funcionalidade ideal, as funções suspend são aplicadas a DAOs compilados em um projeto KMP, exceto os DAOs específicos do Android para manter a compatibilidade com versões anteriores da base de código existente.

Diferenças de recursos com o KMP

Esta seção descreve como os recursos diferem entre as versões do KMP e da plataforma Android do Room.

Funções DAO @RawQuery

As funções anotadas com @RawQuery que são compiladas para plataformas que não são Android vão produzir um erro. Pretendemos adicionar suporte a @RawQuery em uma versão futura do Room.

Callback de consulta

As APIs abaixo para configurar callbacks de consulta não são comuns e, portanto, não estão disponíveis em outras plataformas além do Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

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

A API para configurar um RoomDatabase com um callback de consulta RoomDatabase.Builder.setQueryCallback e a interface de callback RoomDatabase.QueryCallback não estão disponíveis em comum e, portanto, não estão disponíveis em outras plataformas além do 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 não está disponível em outras plataformas.

Banco de dados do pré-pacote

As APIs abaixo para criar um RoomDatabase usando um banco de dados existente (ou seja, um banco de dados pré-empacotado) não estão disponíveis em comum e, portanto, não estão disponíveis em outras plataformas além do Android. São elas:

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

Pretendemos adicionar suporte a bancos de dados pré-empacotados em uma versão futura do Room.

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 no Android e não está disponível em plataformas comuns ou outras plataformas.