Room (Kotlin Multiplatform)

Die Room Persistence Library 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 vorrangig um die Verwendung von Room in Kotlin Multiplatform-Projekten (KMP). Weitere Informationen zur Verwendung von Room finden Sie unter Daten mit Room in einer lokalen Datenbank speichern oder in unseren offiziellen Beispielen.

Abhängigkeiten einrichten

Die aktuelle Version von Room, die KMP unterstützt, ist 2.7.0-alpha01 oder höher.

Fügen Sie die Abhängigkeiten für die Artefakte in der Datei build.gradle.kts für Ihr Modul hinzu, um Room in Ihrem KMP-Projekt einzurichten:

  • androidx.room:room-gradle-plugin – Das Gradle-Plug-in zum Konfigurieren von Raumschemas
  • androidx.room:room-compiler – der KSP-Prozessor, der den Code generiert
  • androidx.room:room-runtime – Laufzeitteil der Bibliothek
  • androidx.sqlite:sqlite-bundled (optional): Die gebündelte SQLite-Bibliothek.

Außerdem müssen Sie den SQLite-Treiber für Room konfigurieren. Diese Treiber unterscheiden sich je nach Zielplattform. Unter Treiberimplementierungen finden Sie Beschreibungen der verfügbaren Treiberimplementierungen.

Hier finden Sie weitere Informationen zur Einrichtung:

Datenbankklassen definieren

Sie müssen eine Datenbankklasse erstellen, die mit @Database zusammen mit DAOs und Entitäten im gemeinsamen Quellsatz des freigegebenen KMP-Moduls annotiert ist. Wenn Sie diese Klassen in gemeinsamen Quellen platzieren, können sie auf allen Zielplattformen gemeinsam genutzt werden.

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

@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
)

Sie können tatsächliche / erwartete Deklarationen verwenden, um plattformspezifische Raumimplementierungen zu erstellen. Sie haben beispielsweise die Möglichkeit, mit expect einen plattformspezifischen DAO hinzuzufügen, der im gemeinsamen Code definiert ist, und dann die actual-Definitionen mit zusätzlichen Abfragen in plattformspezifischen Quellsätzen angeben.

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 bei den Dateisystem-APIs in plattformspezifischen Quellsätzen enthalten sein muss. Bei Android wird der Speicherort der Datenbank normalerweise über die Context.getDatabasePath() API abgerufen. Bei iOS hingegen wird der Speicherort der Datenbank mit NSHomeDirectory abgerufen.

Android

Geben Sie zum Erstellen der Datenbankinstanz einen Kontext zusammen mit dem Datenbankpfad an. Sie müssen keine Datenbank-Factory angeben.

// 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

Geben Sie zum Erstellen der Datenbankinstanz eine Datenbank-Factory zusammen mit dem Datenbankpfad an. Die Datenbank-Factory ist eine Lambda-Funktion, die eine generierte Erweiterungsfunktion mit dem Namen instantiateImpl und einem Empfänger vom Typ KClass<T> aufruft, wobei T der Typ der annotierten Klasse @Database ist.

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM (Desktop)

Geben Sie zum Erstellen der Datenbankinstanz nur den Datenbankpfad an. Sie müssen keine Datenbank-Factory angeben.

// shared/src/commonMain/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,
    )
}

Datenbankinstanziierung

Nachdem Sie die RoomDatabase.Builder von einem der plattformspezifischen Konstruktoren abgerufen haben, können Sie den Rest der Room-Datenbank mit dem gemeinsamen Code zusammen mit der tatsächlichen Datenbankinstanziierung konfigurieren.

// shared/src/commonMain/kotlin/Database.kt

fun getRoomDatabase(
    builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
    return builder
        .addMigrations(MIGRATIONS)
        .fallbackToDestructiveMigrationOnDowngrade()
        .setDriver(BundledSQLiteDriver())
        .setQueryCoroutineContext(Dispatchers.IO)
        .build()
}

SQLiteDriver auswählen

In den vorherigen Code-Snippets wird BundledSQLiteDriver verwendet. Dies ist der empfohlene Treiber, der aus der Quelle kompiliert SQLite enthält. Damit erhalten Sie auf allen Plattformen die konsistentste und aktuellste Version von SQLite. Wenn Sie das vom Betriebssystem bereitgestellte SQLite verwenden möchten, nutzen Sie die setDriver API in plattformspezifischen Quellsätzen, die einen plattformspezifischen Treiber angeben. Für Android können Sie AndroidSQLiteDriver und für iOS NativeSQLiteDriver verwenden. Wenn Sie NativeSQLiteDriver verwenden möchten, müssen Sie eine Verknüpfungsoption angeben, damit die iOS-App dynamisch mit dem SQLite-System 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")
        }
    }
}

Unterschiede

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 geringfügig. Diese Unterschiede sind wie unten aufgeführt und beschrieben.

DAO-Funktionen blockieren

Wenn Sie Room for KMP verwenden, müssen alle DAO-Funktionen, die für Nicht-Android-Plattformen kompiliert wurden, suspend-Funktionen sein. Eine Ausnahme bilden reaktive Rückgabetypen wie 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() { // … }
}

Raum profitiert von der asynchronen kotlinx.coroutines-Bibliothek mit vielen Funktionen, 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 Android-spezifische DAOs, um die Abwärtskompatibilität mit der vorhandenen Codebasis aufrechtzuerhalten.

Funktionsunterschiede zu KMP

In diesem Abschnitt werden die Unterschiede zwischen den KMP- und Android-Plattformversionen von Room beschrieben.

@RawQuery-DAO-Funktionen

Mit @RawQuery annotierte Funktionen, die für Nicht-Android-Plattformen kompiliert wurden, führen zu einem Fehler. Wir beabsichtigen, die Unterstützung für @RawQuery in einer zukünftigen Version von Room hinzuzufügen.

Abfragerückruf

Die folgenden APIs zum Konfigurieren von Abfrage-Callbacks sind nicht gemeinsam und daher auf anderen Plattformen als Android nicht verfügbar.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Wir planen, die Unterstützung für Rückrufanfragen in einer zukünftigen Version von Room hinzuzufügen.

Die API zum Konfigurieren einer RoomDatabase mit einem Abfrage-Callback RoomDatabase.Builder.setQueryCallback und der Callback-Schnittstelle RoomDatabase.QueryCallback ist nicht gemeinsam und daher auch nicht auf anderen Plattformen als Android verfügbar.

Datenbank automatisch schließen

Die API zum Aktivieren des automatischen Schließens nach einer Zeitüberschreitung,RoomDatabase.Builder.setAutoCloseTimeout, ist nur unter Android und auf anderen Plattformen nicht verfügbar.

Vorpaketierte Datenbank

Die folgenden APIs zum Erstellen einer RoomDatabase unter Verwendung einer vorhandenen Datenbank (d.h. einer vorgefertigten Datenbank) sind nicht gemeinsam und daher auch nicht auf anderen Plattformen als Android verfügbar. Diese APIs sind:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

Wir beabsichtigen, in einer zukünftigen Version von Room Support für vorkonfigurierte Datenbanken zu unterstützen.

Entwertung mehrerer Instanzen

Die API zum Aktivieren der Entwertung mehrerer Instanzen (RoomDatabase.Builder.enableMultiInstanceInvalidation) ist nur unter Android und weder auf gängigen noch auf anderen Plattformen verfügbar.