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.
Como configurar dependências
A versão atual do Room que oferece suporte ao KMP é 2.7.0-alpha01 ou mais recente.
Para configurar o Room no projeto do KMP, adicione as dependências dos artefatos no
arquivo build.gradle.kts
do módulo:
androidx.room:room-gradle-plugin
: o plug-in do Gradle para configurar esquemas do Roomandroidx.room:room-compiler
: o processador KSP que gera código.androidx.room:room-runtime
: a parte de tempo de execução da bibliotecaandroidx.sqlite:sqlite-bundled
: (opcional) a biblioteca SQLite incluída
Além disso, você precisa configurar o driver SQLite do Room. Esses drivers são diferentes de acordo com a plataforma de destino. Consulte Implementações de driver para ver descrições das implementações de driver disponíveis.
Para mais informações sobre a configuração, consulte:
- Defina o local do esquema usando o plug-in do Gradle para Room.
- KSP com Kotlin Multiplatform.
- Como adicionar dependências de execução.
Como definir as classes do banco de dados
É necessário 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 origens comuns permite que elas sejam compartilhadas em todas as plataformas
de destino.
Quando você declara um objeto expect
com a interface
RoomDatabaseConstructor
, o compilador do Room gera as implementações
actual
. O Android Studio pode emitir um aviso
"Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
. Você pode suprimir o aviso com @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
)
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 definido em código comum usando expect
e, em seguida,
especificar as definições de actual
com consultas adicionais em conjuntos de origem
específicos da plataforma.
Como criar o criador 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,
a localização do banco de dados geralmente é obtida pela
API Context.getDatabasePath()
. Já no iOS, a localização do banco de dados é
obtida usando NSFileManager
.
Android
Para criar a instância do banco de dados, especifique um Contexto com o caminho do 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 um caminho usando o
NSFileManager
, geralmente localizado no 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 do banco de dados usando APIs Java ou 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,
)
}
Minificação e ofuscação
Se o projeto for minimizado ou ofuscado, a regra de proguard abaixo precisará ser incluída para que o Room encontre a implementação gerada da definição do banco de dados:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Instância de banco de dados
Depois de receber 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 que inclui o SQLite compilado da origem, que fornece a
versão mais consistente e atualizada do SQLite em todas as plataformas. Se você
querer usar o SQLite fornecido pelo SO, use a API setDriver
em conjuntos de origem
específicos da plataforma que especificam um driver específico da plataforma. No Android, use
AndroidSQLiteDriver
. No iOS, use NativeSQLiteDriver
. Para
usar NativeSQLiteDriver
, é necessário fornecer uma opção de linker para que o app
iOS se conecte 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 do Android e depois foi migrado para o KMP com foco na compatibilidade da API. A versão do Room para KMP é um pouco diferente entre plataformas e da versão específica do Android. Essas diferenças são listadas e descritas a seguir.
Funções de bloqueio do 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
, exceto 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 com muitos recursos
que o Kotlin oferece para várias plataformas. Para uma funcionalidade ideal, as funções suspend
são aplicadas a DAOs compiladas em um projeto KMP, com exceção de
DAOs específicas do Android para manter a compatibilidade com a versão anterior do
código-base.
Diferenças de recursos com o KMP
Esta seção descreve as diferenças entre os recursos do Room nas versões do KMP e da plataforma Android.
Funções DAO @RawQuery
As funções com anotação @RawQuery
que são compiladas para plataformas que não são do Android
precisam 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 de 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)
}
Chamada de retorno de consulta
As APIs a seguir 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 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, exceto 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
não em outras plataformas.
Banco de dados pré-empacotado
As APIs a seguir 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. Essas APIs são:
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.