Die Room-Persistenzbibliothek bietet eine Abstraktionsebene über SQLite, um einen robusteren Datenbankzugriff zu ermöglichen und gleichzeitig die volle Leistung von SQLite zu nutzen. Auf dieser Seite geht es um die Verwendung von Room in Kotlin Multiplatform (KMP) -Projekten. Weitere Informationen zur Verwendung von Room finden Sie unter Daten in einer lokalen Datenbank mit Room speichern oder in unseren offiziellen Beispielen.
Abhängigkeiten einrichten
Wenn Sie Room in Ihrem KMP-Projekt einrichten möchten, fügen Sie die Abhängigkeiten für die Artefakte in der Datei build.gradle.kts für Ihr KMP-Modul hinzu.
Definieren Sie die Abhängigkeiten in der Datei 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" }
Fügen Sie das Room-Gradle-Plug-in hinzu, um Room-Schemas und das KSP Plug-in zu konfigurieren.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Fügen Sie die Room-Laufzeitabhängigkeit und die gebündelte SQLite-Bibliothek hinzu:
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)
}
Fügen Sie die KSP-Abhängigkeiten dem Stammblock dependencies hinzu. Sie müssen alle Ziele hinzufügen, die Ihre App verwendet. Weitere Informationen finden Sie unter KSP mit
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
}
Definieren Sie das Room-Schemaverzeichnis. Weitere Informationen finden Sie unter Schemaspeicherort mit dem Room-Gradle-Plug-in festlegen.
room {
schemaDirectory("$projectDir/schemas")
}
Datenbankklassen definieren
Sie müssen eine Datenbankklasse mit der Annotation @Database sowie DAOs und Entitäten im gemeinsamen Source-Set Ihres freigegebenen KMP-Moduls erstellen. Wenn Sie diese Klassen in gemeinsamen Quellen platzieren, können sie auf allen Zielplattformen verwendet werden.
// 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
}
Wenn Sie ein expect-Objekt mit der Schnittstelle RoomDatabaseConstructor deklarieren, generiert der Room-Compiler die actual-Implementierungen. In Android Studio wird möglicherweise die folgende Warnung angezeigt, die Sie mit
unterdrücken können: @Suppress("KotlinNoActualForExpect")
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
Definieren Sie als Nächstes entweder eine neue DAO-Schnittstelle oder verschieben Sie eine vorhandene nach
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>>
}
Definieren oder verschieben Sie Ihre Entitäten in commonMain:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
Plattformspezifischen Datenbank-Builder erstellen
Sie müssen einen Datenbank-Builder definieren, um Room auf jeder Plattform zu instanziieren. Dies ist der einzige Teil der API, der aufgrund der Unterschiede in den Dateisystem-APIs in plattformspezifischen Quellsätzen enthalten sein muss.
Android
Unter Android wird der Datenbankspeicherort in der Regel über die
Context.getDatabasePath() API abgerufen. Geben Sie zum Erstellen der Datenbankinstanz einen
Context zusammen mit dem Datenbankpfad an.
// 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
Um die Datenbankinstanz unter iOS zu erstellen, geben Sie einen Datenbankpfad mit dem
NSFileManager an, der sich normalerweise in NSDocumentDirectory befindet.
// 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)
Geben Sie zum Erstellen der Datenbankinstanz einen Datenbankpfad mit Java- oder Kotlin-APIs an.
// 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,
)
}
Datenbank instanziieren
Sobald Sie den RoomDatabase.Builder von einem der plattformspezifischen Konstruktoren erhalten haben, können Sie den Rest der Room-Datenbank im gemeinsamen Code zusammen mit der tatsächlichen Datenbankinstanziierung konfigurieren.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
SQLite-Treiber auswählen
Im vorherigen Code-Snippet wird die Builder-Funktion setDriver aufgerufen, um zu definieren, welchen SQLite-Treiber die Room-Datenbank verwenden soll. Diese Treiber unterscheiden sich je nach Zielplattform. In den vorherigen Code-Snippets wird BundledSQLiteDriver verwendet.
Dies ist der empfohlene Treiber, der aus der Quelle kompilierte SQLite enthält und die konsistenteste und aktuellste Version von SQLite auf allen Plattformen bietet.
Wenn Sie das vom Betriebssystem bereitgestellte SQLite verwenden möchten, verwenden Sie die API setDriver in den plattformspezifischen Quellsätzen, die einen plattformspezifischen Treiber angeben. Unter
Treiberimplementierungen finden Sie Beschreibungen der verfügbaren Treiber
implementierungen. Sie haben folgende Möglichkeiten:
AndroidSQLiteDriverinandroidMainNativeSQLiteDriveriniosMain
Wenn Sie NativeSQLiteDriver verwenden möchten, müssen Sie eine Linker-Option -lsqlite3 angeben, damit die iOS-App dynamisch mit dem System-SQLite verknüpft wird.
// 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")
}
}
}
Coroutine-Kontext festlegen (optional)
Ein RoomDatabase-Objekt unter Android kann optional mit freigegebenen
Anwendungsexecutors konfiguriert werden, indem RoomDatabase.Builder.setQueryExecutor() verwendet wird, um
Datenbankvorgänge auszuführen.
Da Executors nicht mit KMP kompatibel sind, ist die setQueryExecutor()-API von Room in commonMain nicht verfügbar. Stattdessen muss das RoomDatabase-Objekt mit einem CoroutineContext konfiguriert werden, der mit RoomDatabase.Builder.setCoroutineContext() festgelegt werden kann. Wenn kein Kontext festgelegt ist, verwendet das RoomDatabase-Objekt standardmäßig Dispatchers.IO.
Minifizierung und Verschleierung
Wenn das Projekt minifiziert oder verschleiert ist, müssen Sie die folgende ProGuard-Regel einfügen, damit Room die generierte Implementierung der Datenbankdefinition finden kann:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Zu Kotlin Multiplatform migrieren
Room wurde ursprünglich als Android-Bibliothek entwickelt und später zu KMP migriert, wobei der Schwerpunkt auf der API-Kompatibilität lag. Die KMP-Version von Room unterscheidet sich je nach Plattform und von der Android-spezifischen Version. Diese Unterschiede werden im Folgenden aufgeführt und beschrieben.
Von Support SQLite zu SQLite Driver migrieren
Alle Verwendungen von SupportSQLiteDatabase und anderen APIs in
androidx.sqlite.db müssen mit SQLite Driver APIs refaktoriert werden,
da die APIs in androidx.sqlite.db nur für Android verfügbar sind (beachten Sie das
andere Paket als das KMP-Paket).
Aus Gründen der Abwärtskompatibilität und solange RoomDatabase mit einer SupportSQLiteOpenHelper.Factory konfiguriert ist (z. B. kein SQLiteDriver festgelegt ist), verhält sich Room im Kompatibilitätsmodus, in dem sowohl Support SQLite- als auch SQLite Driver APIs wie erwartet funktionieren. Dies ermöglicht inkrementelle Migrationen, sodass Sie nicht alle Verwendungen von Support SQLite in einer einzigen Änderung zu SQLite Driver konvertieren müssen.
Room SQLite Wrapper verwenden (optional)
Das Artefakt androidx.room:room-sqlite-wrapper bietet APIs, um während der Migration eine Brücke zwischen SQLiteDriver und SupportSQLiteDatabase zu schlagen.
Wenn Sie eine SupportSQLiteDatabase aus einer RoomDatabase abrufen möchten, die mit einem
SQLiteDriver konfiguriert ist, verwenden Sie die neue Erweiterungsfunktion
RoomDatabase.getSupportWrapper(). Dieser Kompatibilitäts-Wrapper hilft dabei, vorhandene Verwendungen von SupportSQLiteDatabase (oft von RoomDatabase.openHelper.writableDatabase abgerufen) beizubehalten, während SQLiteDriver übernommen wird. Dies ist besonders nützlich für Codebasen mit umfangreichen SupportSQLite API-Verwendungen, die BundledSQLiteDriver verwenden möchten.
Unterklassen für Migrationen konvertieren
Unterklassen für Migrationen müssen zu den entsprechenden SQLite-Treibern migriert werden:
Kotlin Multiplatform
Unterklassen für Migrationen
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
Unterklassen für automatische Migrationsspezifikationen
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
Nur für Android
Unterklassen für Migrationen
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
Unterklassen für automatische Migrationsspezifikationen
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
Datenbank-Callback konvertieren
Datenbank-Callbacks müssen zu den entsprechenden SQLite-Treibern migriert werden:
Kotlin Multiplatform
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
Nur für Android
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
@RawQuery-DAO-Funktionen konvertieren
Für Funktionen mit der Annotation @RawQuery, die für Nicht-Android-Plattformen kompiliert werden, muss ein Parameter vom Typ RoomRawQuery anstelle von SupportSQLiteQuery deklariert werden.
Kotlin Multiplatform
Rohabfrage definieren
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
Mit RoomRawQuery kann dann zur Laufzeit eine Abfrage erstellt werden:
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)
}
Nur für Android
Rohabfrage definieren
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
Mit SimpleSQLiteQuery kann dann zur Laufzeit eine Abfrage erstellt werden:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
Blockierende DAO-Funktionen konvertieren
Room profitiert von der funktionsreichen asynchronen kotlinx.coroutines-Bibliothek, die Kotlin für mehrere Plattformen bietet. Für eine optimale Funktionalität werden suspend-Funktionen für DAOs erzwungen, die in einem KMP-Projekt kompiliert wurden. Eine Ausnahme bilden DAOs, die in androidMain implementiert wurden, um die Abwärtskompatibilität mit der vorhandenen Codebasis beizubehalten. Wenn Sie Room für KMP verwenden, müssen alle DAO-Funktionen, die für Nicht-Android-Plattformen kompiliert wurden, suspend-Funktionen sein.
Kotlin Multiplatform
Abfragen anhalten
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Transaktionen anhalten
@Transaction
suspend fun transaction() { … }
Nur für Android
Blockierende Abfragen
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Blockierende Transaktionen
@Transaction
fun blockingTransaction() { … }
Reaktive Typen zu Flow konvertieren
Nicht alle DAO-Funktionen müssen „suspend“-Funktionen sein. DAO-Funktionen, die reaktive Typen wie LiveData oder Flowable von RxJava zurückgeben, sollten nicht in „suspend“-Funktionen konvertiert werden. Einige Typen wie LiveData sind jedoch nicht mit KMP kompatibel. DAO-Funktionen mit reaktiven Rückgabetypen müssen zu Coroutine-Flows migriert werden.
Kotlin Multiplatform
Reaktive Typen Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Nur für Android
Reaktive Typen wie LiveData oder Flowable von RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Transaktions-APIs konvertieren
Datenbanktransaktions-APIs für Room KMP können zwischen Schreibtransaktionen (useWriterConnection) und Lesetransaktionen (useReaderConnection) unterscheiden.
Kotlin Multiplatform
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Nur für Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Schreibtransaktionen
Verwenden Sie Schreibtransaktionen, um sicherzustellen, dass mehrere Abfragen Daten atomar schreiben, sodass Leser konsistent auf die Daten zugreifen können. Dazu können Sie useWriterConnection mit einem der drei Transaktionstypen verwenden:
immediateTransaction: Im WAL-Modus (Write-Ahead Logging) (Standard) wird bei dieser Art von Transaktion beim Start eine Sperre erworben, aber Leser können weiterhin lesen. Dies ist in den meisten Fällen die bevorzugte Wahl.deferredTransaction: Die Transaktion erwirbt erst bei der ersten Schreibanweisung eine Sperre. Verwenden Sie diese Art von Transaktion als Optimierung, wenn Sie nicht sicher sind, ob innerhalb der Transaktion ein Schreibvorgang erforderlich ist. Wenn Sie beispielsweise eine Transaktion starten, um Songs aus einer Playlist zu löschen, und nur der Name der Playlist angegeben ist und die Playlist nicht vorhanden ist, ist kein Schreibvorgang (Löschen) erforderlich.exclusiveTransaction: Dieser Modus verhält sich im WAL-Modus identisch mitimmediateTransaction. In anderen Journaling-Modi wird verhindert, dass andere Datenbankverbindungen die Datenbank lesen, während die Transaktion ausgeführt wird.
Lesetransaktionen
Verwenden Sie Lesetransaktionen, um mehrmals konsistent aus der Datenbank zu lesen. Das ist beispielsweise der Fall, wenn Sie zwei oder mehr separate Abfragen haben und keine JOIN-Klausel verwenden. In Leseverbindungen sind nur verzögerte Transaktionen zulässig. Wenn Sie versuchen, in einer Leseverbindung eine sofortige oder exklusive Transaktion zu starten, wird eine Ausnahme ausgelöst, da diese als Schreibvorgänge betrachtet werden.
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Nicht in Kotlin Multiplatform verfügbar
Einige der APIs, die für Android verfügbar waren, sind in Kotlin Multiplatform nicht verfügbar.
Abfrage-Callback
Die folgenden APIs zum Konfigurieren von Abfrage-Callbacks sind in „Common“ nicht verfügbar und daher auch nicht auf anderen Plattformen als Android.
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
Wir planen, in einer zukünftigen Version von Room Unterstützung für Abfrage-Callbacks hinzuzufügen.
Die API zum Konfigurieren einer RoomDatabase mit einem Abfrage-Callback
RoomDatabase.Builder.setQueryCallback sowie die Callback-Oberfläche
RoomDatabase.QueryCallback sind in „Common“ nicht verfügbar und daher auch nicht auf anderen Plattformen als Android verfügbar.
Automatisches Schließen der Datenbank
Die API zum Aktivieren des automatischen Schließens nach einem Timeout, RoomDatabase.Builder.setAutoCloseTimeout, ist nur unter Android verfügbar und nicht auf anderen Plattformen.
Vorkonfigurierte Datenbank
Die folgenden APIs zum Erstellen einer RoomDatabase mit einer vorhandenen Datenbank (d.h. einer vorkonfigurierten Datenbank) sind in „Common“ nicht verfügbar und daher auch nicht auf anderen Plattformen als Android. Diese APIs sind:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
Wir planen, in einer zukünftigen Version von Room Unterstützung für vorkonfigurierte Datenbanken hinzuzufügen.
Entwertung für mehrere Instanzen
Die API zum Aktivieren der Entwertung im Multi-Instanz-Modus, RoomDatabase.Builder.enableMultiInstanceInvalidation, ist nur unter Android verfügbar und nicht in „Common“ oder auf anderen Plattformen.
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Codelab: Vorhandene Apps zu Room KMP migrieren
- Codelab: Erste Schritte mit KMP
- Daten mit Room in einer lokalen Datenbank speichern