A biblioteca de persistência do 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 do 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.
Configurar dependências
Para configurar o Room no seu projeto do KMP, adicione as dependências dos artefatos no arquivo build.gradle.kts do módulo do KMP.
Defina as dependências no arquivo libs.versions.toml:
[versions]
room = "2.8.4"
sqlite = "2.6.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
Adicione o plug-in do Gradle para Room para configurar esquemas do Room e o plug-in da KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Adicione a dependência de ambiente de execução do Room e a biblioteca SQLite agrupada:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
Adicione as dependências da KSP ao bloco dependencies raiz. Observação: adicione todos os destinos usados pelo app. Para mais informações, consulte KSP com
Kotlin Multiplatform.
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Defina o diretório de esquema do Room. Para mais informações, consulte Definir o local do esquema usando o plug-in do Gradle para Room.
room {
schemaDirectory("$projectDir/schemas")
}
Definir as classes de banco de dados
É necessário criar uma classe de banco de dados anotada com @Database, além de DAOs e entidades no conjunto de origem comum do módulo compartilhado do KMP. Colocar essas classes em origens comuns permite que elas sejam compartilhadas em todas as plataformas de destino.
// 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("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
Quando você declara um objeto expect com a interface RoomDatabaseConstructor, o compilador do Room gera as implementações actual. O Android Studio pode emitir o seguinte aviso, que pode ser
suprimido com @Suppress("KotlinNoActualForExpect"):
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
Em seguida, defina uma nova interface DAO ou mova uma interface existente para
commonMain:
// shared/src/commonMain/kotlin/TodoDao.kt
@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>>
}
Defina ou mova suas entidades para commonMain:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
Criar o builder de banco de dados específico da plataforma
É 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.
Android
No Android, o local do banco de dados é geralmente obtido pela
Context.getDatabasePath() API. Para criar a instância do banco de dados, especifique um
Context junto com o caminho do banco de dados.
// shared/src/androidMain/kotlin/Database.android.kt
fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = context.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 no iOS, forneça um caminho de banco de dados usando o
NSFileManager, geralmente localizado no NSDocumentDirectory.
// shared/src/iosMain/kotlin/Database.ios.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 (desktop)
Para criar a instância do banco de dados, forneça um caminho de banco de dados usando APIs Java ou Kotlin.
// shared/src/jvmMain/kotlin/Database.desktop.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
Instanciar o banco de dados
Depois de receber o RoomDatabase.Builder de um dos construtores específicos da plataforma, você poderá configurar o restante do banco de dados do Room no código comum junto com a instanciação real do banco de dados.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
Selecionar um driver SQLite
O snippet de código anterior chama a função do builder setDriver para definir qual driver SQLite o banco de dados do Room deve usar. Esses drivers variam de acordo com a plataforma segmentada. 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ê quiser usar o SQLite fornecido pelo SO, use a API setDriver nos conjuntos de origem específicos da plataforma que especificam um driver específico da plataforma. Consulte
Implementações de driver para descrições das implementações de driver disponíveis. É possível usar uma das seguintes opções:
AndroidSQLiteDriveremandroidMainNativeSQLiteDriveremiosMain
Para usar NativeSQLiteDriver, é necessário fornecer uma opção de vinculador -lsqlite3 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")
}
}
}
Definir um contexto de corrotina (opcional)
Um objeto RoomDatabase no Android pode ser configurado opcionalmente com executores de aplicativos compartilhados usando RoomDatabase.Builder.setQueryExecutor() para realizar operações de banco de dados.
Como os executores não são compatíveis com o KMP, a API setQueryExecutor() do Room não está disponível em commonMain. Em vez disso, o objeto RoomDatabase precisa ser configurado
com um CoroutineContext, que pode ser definido usando
RoomDatabase.Builder.setCoroutineContext(). Se nenhum contexto for definido, o objeto RoomDatabase usará Dispatchers.IO por padrão.
Minificação e ofuscação
Se o projeto for minificado ou ofuscado, inclua a seguinte regra do ProGuard para que o Room possa encontrar a implementação gerada da definição do banco de dados:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Migrar para o Kotlin Multiplatform
O Room foi originalmente desenvolvido como uma biblioteca do Android e, posteriormente, migrado para o KMP com foco na compatibilidade da API. A versão do KMP do Room difere um pouco entre plataformas e da versão específica do Android. Essas diferenças são listadas e descritas da seguinte maneira.
Migrar do SQLite de suporte para o driver SQLite
Todos os usos de SupportSQLiteDatabase e outras APIs em
androidx.sqlite.db precisam ser refatorados com APIs do driver SQLite,
porque as APIs em androidx.sqlite.db são exclusivas do Android (observe o
pacote diferente do pacote KMP).
Para garantir a compatibilidade com versões anteriores e desde que o RoomDatabase esteja configurado com um SupportSQLiteOpenHelper.Factory (por exemplo, nenhum SQLiteDriver esteja definido), o Room se comporta no "modo de compatibilidade", em que as APIs do SQLite de suporte e do driver SQLite funcionam conforme o esperado. Isso permite migrações incrementais para que você não precise converter todos os usos do SQLite de suporte para o driver SQLite em uma única mudança.
Usar o wrapper do SQLite do Room (opcional)
O artefato androidx.room:room-sqlite-wrapper fornece APIs para fazer a ponte entre SQLiteDriver e SupportSQLiteDatabase durante a migração.
Para receber um SupportSQLiteDatabase de um RoomDatabase configurado com um
SQLiteDriver, use a nova função de extensão
RoomDatabase.getSupportWrapper(). Esse wrapper de compatibilidade ajuda a manter
os usos atuais de SupportSQLiteDatabase (geralmente obtidos de
RoomDatabase.openHelper.writableDatabase) ao adotar SQLiteDriver,
especialmente para bases de código com usos extensos da API SupportSQLite que querem
usar BundledSQLiteDriver.
Converter subclasses de migrações
As subclasses de migrações precisam ser migradas para as contrapartes do driver SQLite:
Kotlin Multiplatform
Subclasses de migração
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
Subclasses de especificação de migração automática
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
Exclusivo do Android
Subclasses de migração
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
Subclasses de especificação de migração automática
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
Converter callback de banco de dados
Os callbacks de banco de dados precisam ser migrados para as contrapartes do driver SQLite:
Kotlin Multiplatform
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
Exclusivo do Android
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
Converter funções DAO @RawQuery
As funções anotadas com @RawQuery que são compiladas para plataformas não Android precisam declarar um parâmetro do tipo RoomRawQuery em vez de SupportSQLiteQuery.
Kotlin Multiplatform
Definir a consulta bruta
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
Um RoomRawQuery pode ser usado para criar uma consulta no ambiente de execução:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?",
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todoDao().getTodos(query)
}
Exclusivo do Android
Definir a consulta bruta
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
Um SimpleSQLiteQuery pode ser usado para criar uma consulta no ambiente de execução:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
Converter funções DAO de bloqueio
O Room se beneficia da biblioteca assíncrona kotlinx.coroutines, rica em recursos, que o Kotlin oferece para várias plataformas. Para uma funcionalidade ideal, as funções suspend são aplicadas para DAOs compilados em um projeto do KMP, com exceção dos DAOs implementados em androidMain para manter a compatibilidade com versões anteriores da base de código existente. Ao usar o Room para KMP, todas as funções DAO compiladas para plataformas não Android precisam ser funções suspend.
Kotlin Multiplatform
Consultas de suspensão
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Transações de suspensão
@Transaction
suspend fun transaction() { … }
Exclusivo do Android
Consultas de bloqueio
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Transações de bloqueio
@Transaction
fun blockingTransaction() { … }
Converter tipos reativos em Flow
Nem todas as funções DAO precisam ser funções de suspensão. As funções DAO que retornam tipos reativos, como LiveData ou Flowable do RxJava, não devem ser convertidas em funções de suspensão. No entanto, alguns tipos, como LiveData, não são compatíveis com o KMP. As funções DAO com tipos de retorno reativos precisam ser migradas para fluxos de corrotina.
Kotlin Multiplatform
Tipos reativos Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Exclusivo do Android
Tipos reativos como LiveData ou Flowable do RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Converter APIs de transação
As APIs de transação de banco de dados para o Room KMP podem diferenciar entre transações de gravação (useWriterConnection) e leitura (useReaderConnection).
Kotlin Multiplatform
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Exclusivo do Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Gravar transações
Use transações de gravação para garantir que várias consultas gravem dados de forma atômica, para que os leitores possam acessar os dados de maneira consistente. É possível fazer isso usando useWriterConnection com um dos três tipos de transação:
immediateTransaction: no modo registro prévio de escrita (WAL, na sigla em inglês) (padrão), esse tipo de transação adquire um bloqueio quando começa, mas os leitores podem continuar lendo. Essa é a opção preferida para a maioria dos casos.deferredTransaction: a transação não vai adquirir um bloqueio até a primeira instrução de gravação. Use esse tipo de transação como uma otimização quando não tiver certeza se uma operação de gravação será necessária na transação. Por exemplo, se você iniciar uma transação para excluir músicas de uma playlist com apenas um nome e a playlist não existir, nenhuma operação de gravação (exclusão) será necessária.exclusiveTransaction: esse modo se comporta de maneira idêntica aimmediateTransactionno modo WAL. Em outros modos de registro no diário, ele impede que outras conexões de banco de dados leiam o banco de dados enquanto a transação estiver em andamento.
Ler transações
Use transações de leitura para ler do banco de dados várias vezes de maneira consistente. Por exemplo, quando você tem duas ou mais consultas separadas e não usa uma cláusula JOIN. Somente transações adiadas são permitidas em conexões de leitor. A tentativa de iniciar uma transação imediata ou exclusiva em uma conexão de leitor vai gerar uma exceção, já que elas são consideradas operações de "gravação".
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Não disponível no Kotlin Multiplatform
Algumas das APIs que estavam disponíveis para o Android não estão disponíveis no Kotlin Multiplatform.
Callback 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.setQueryCallbackRoomDatabase.QueryCallback
Pretendemos adicionar suporte para callback de consulta em uma versão futura do Room.
A API para configurar um RoomDatabase com um callback de consulta
RoomDatabase.Builder.setQueryCallback juntamente com a interface de callback
RoomDatabase.QueryCallback não está disponível em comum e, portanto, não está disponível
em outras plataformas que não sejam 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 está disponível em outras plataformas.
Banco de dados pré-empacotado
As APIs a seguir para criar um RoomDatabase usando um banco de dados atual (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 que não sejam o Android. Essas APIs são:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
Pretendemos adicionar suporte para 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 comum ou em outras plataformas.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Migrar apps atuais para o KMP do Room (codelab, link em inglês)
- Codelab: primeiros passos com o KMP (link em inglês)
- Salvar dados no banco de dados local com o Room