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 bibliotecaandroidx.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:
- Definir o local do esquema usando o plug-in do Gradle para Room.
- KSP com Kotlin Multiplatform.
- Como adicionar dependências de ambiente de execução.
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.
Quando você declara um objeto expect
com a interface
RoomDatabaseConstructor
, o compilador do Room gera o actual
.
e implementações. O Android Studio pode emitir um aviso
"Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
é possível 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
)
Também é 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
construtores, é possível configurar o restante do banco de dados do Room em 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.