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 nossas amostras oficiais.
Configurar dependências
Para configurar o Room no seu projeto KMP, adicione as dependências dos artefatos no
arquivo build.gradle.kts
do módulo KMP.
Defina as dependências no arquivo libs.versions.toml
:
[versions]
room = "2.7.2"
sqlite = "2.5.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 KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Adicione a dependência de tempo 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 do KSP ao bloco dependencies
root. Adicione todas as metas usadas 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 do 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
Você precisa criar uma classe de banco de dados com a anotação @Database
, além de DAOs e entidades no conjunto de origem comum do módulo compartilhado do KMP. 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)
@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 de 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 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 criador de banco de dados específico da plataforma
Você precisa definir um criador 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 API
Context.getDatabasePath()
. 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 de banco de dados no iOS, forneça um caminho de banco de dados usando o
NSFileManager
, geralmente localizado em 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 (computador)
Para criar a instância do banco de dados, forneça um caminho 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, é possível configurar o restante do banco de dados Room em um 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 do SQLite
O snippet de código anterior chama a função de builder setDriver
para definir qual
driver SQLite o banco de dados Room deve usar. Esses drivers variam de acordo com a
plataforma de destino. Os snippets de código anteriores usam o BundledSQLiteDriver
.
Esse é o driver recomendado, que inclui o SQLite compilado da origem e
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. Você pode usar uma das seguintes opções:
AndroidSQLiteDriver
emandroidMain
NativeSQLiteDriver
emiosMain
Para usar NativeSQLiteDriver
, forneça 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 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
vai 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 depois migrado para KMP com foco na compatibilidade da API. A versão 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 forma.
Migrar do Support SQLite para o driver SQLite
Todos os usos de SupportSQLiteDatabase
e outras APIs em
androidx.sqlite.db
precisam ser refatorados com as 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
está
definido), o Room se comporta no "modo de compatibilidade", em que as APIs do Support SQLite e do
SQLite Driver funcionam como esperado. Isso permite migrações incrementais para que
não seja necessário converter todos os usos do Support SQLite para o driver SQLite em uma
única mudança.
Converter subclasses de migrações
As subclasses de migrações precisam ser migradas para as contrapartes de driver do 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) {
// …
}
}
Somente 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) {
// …
}
}
Callback de conversão do 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) {
// …
}
}
Somente 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
e compiladas para plataformas que não sã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 tempo 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)
}
Somente 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 tempo 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 a DAOs compilados em um projeto KMP, exceto
DAOs implementados em androidMain
para manter a compatibilidade com versões anteriores do
código-fonte atual. Ao usar o Room para KMP, todas as funções de DAO compiladas para
plataformas não Android precisam ser funções suspend
.
Kotlin Multiplatform
Como suspender consultas
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Suspender transações
@Transaction
suspend fun transaction() { … }
Somente Android
Consultas de bloqueio
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Bloqueio de transações
@Transaction
fun blockingTransaction() { … }
Converter tipos reativos em Flow
Nem todas as funções de DAO precisam ser funções de suspensão. 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 KMP. As funções DAO com tipos de retorno reativos precisam ser migradas para fluxos de
corrotinas.
Kotlin Multiplatform
Tipos reativos Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Somente Android
Tipos reativos, como LiveData
ou Flowable
do RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Converter APIs Transaction
As APIs de transação de banco de dados para KMP do Room 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
}
}
Somente 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 atomicamente,
para que os leitores possam acessar os dados de forma consistente. Para isso, use
useWriterConnection
com qualquer um dos três tipos de transação:
immediateTransaction
: no modo registro prévio de escrita (WAL) (padrão), esse tipo de transação adquire um bloqueio quando começa, mas os leitores podem continuar lendo. Essa é a opção preferida na 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 dentro da transação. Por exemplo, se você iniciar uma transação para excluir músicas de uma playlist usando apenas o nome dela, e ela não existir, nenhuma operação de gravação (exclusão) será necessária.exclusiveTransaction
: esse modo se comporta de maneira idêntica aimmediateTransaction
no modo WAL. Em outros modos de registro em diário, ele impede que outras conexões de banco de dados leiam o banco enquanto a transação está em andamento.
Ler transações
Use transações de leitura para ler do banco de dados várias vezes de forma 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 leitura. A tentativa de iniciar uma transação imediata ou exclusiva em uma conexão de leitor vai gerar uma exceção, já que essas 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 disponíveis para 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.setQueryCallback
RoomDatabase.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
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 em outras plataformas.
Pré-pacote de banco de dados
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. Estas são as APIs:
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 em plataformas comuns ou outras.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Codelab: migrar apps atuais para o KMP do Room
- Codelab: Primeiros passos com o KMP
- Salvar dados no banco de dados local com o Room