Room (Kotlin Multiplatform)

Die Room-Persistenzbibliothek bietet eine Abstraktionsschicht über SQLite, die einen robusteren Datenbankzugriff ermöglicht und gleichzeitig die volle Leistung von SQLite nutzt. Auf dieser Seite geht es hauptsächlich 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.

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 Modul hinzu:

  • androidx.room:room-gradle-plugin – das Gradle-Plug-in zum Konfigurieren von Room-Schemas
  • androidx.room:room-compiler – KSP-Prozessor, der Code generiert
  • androidx.room:room-runtime: Der Laufzeitteil der Bibliothek
  • androidx.sqlite:sqlite-bundled – (Optional) Die mitgelieferte SQLite-Bibliothek

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

Weitere Informationen zur Einrichtung finden Sie hier:

Datenbankklassen definieren

Sie müssen eine mit @Database annotierte Datenbankklasse sowie DAOs und Entitäten im gemeinsamen Quellsatz Ihres freigegebenen KMP-Moduls erstellen. Wenn Sie diese Klassen in gemeinsamen Quellen platzieren, können sie auf allen Zielplattformen freigegeben werden.

Wenn Sie ein expect-Objekt mit der Schnittstelle RoomDatabaseConstructor deklarieren, generiert der Room-Compiler die actual-Implementierungen. Android Studio sendet möglicherweise die Warnung "Expected object 'AppDatabaseConstructor' has no actual declaration in module". Sie können sie mit @Suppress("NO_ACTUAL_FOR_EXPECT") unterdrücken.

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

Optional können Sie actual-/expect-Deklarationen verwenden, um plattformspezifische Room-Implementierungen zu erstellen. Sie können beispielsweise eine plattformspezifische DAO hinzufügen, die in gemeinsamem Code mit expect 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 Dateisystem-APIs in platformspezifischen Quellsätzen enthalten sein muss. Unter Android wird der Speicherort der Datenbank beispielsweise in der Regel über die Context.getDatabasePath() API abgerufen, während er unter iOS mit NSFileManager abgerufen wird.

Android

Geben Sie zum Erstellen der Datenbankinstanz einen Kontext und den Pfad zur Datenbank an.

// 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 einen Datenbankpfad mithilfe von NSFileManager an, der sich normalerweise im NSDocumentDirectory befindet.

// 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 (Desktop)

Geben Sie zum Erstellen der Datenbankinstanz einen Datenbankpfad mit Java- oder Kotlin-APIs an.

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

Minimierung und Verschleierung

Wenn das Projekt minimiert oder verschleiert ist, muss die folgende ProGuard-Regel enthalten sein, damit Room die generierte Implementierung der Datenbankdefinition finden kann:

-keep class * extends androidx.room.RoomDatabase { <init>(); }

Datenbankinstanzierung

Nachdem Sie die RoomDatabase.Builder über einen der plattformspezifischen Konstruktoren abgerufen haben, können Sie den Rest der Room-Datenbank in gemeinsamem Code zusammen mit der tatsächlichen Datenbankinstanz 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()
}

SQLite-Treiber auswählen

In den vorherigen Code-Snippets wird die BundledSQLiteDriver verwendet. Dies ist der empfohlene Treiber, der SQLite enthält, das aus der Quelle kompiliert wurde. Er bietet die konsistenteste und aktuellste Version von SQLite auf allen Plattformen. Wenn Sie die vom Betriebssystem bereitgestellte SQLite-Version verwenden möchten, verwenden Sie die setDriver API in plattformspezifischen Quellsätzen, die einen plattformspezifischen Treiber angeben. Unter Android können Sie die Taste AndroidSQLiteDriver verwenden, unter iOS die Taste NativeSQLiteDriver. Wenn Sie NativeSQLiteDriver verwenden möchten, müssen Sie eine Linker-Option 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 mit Schwerpunkt auf API-Kompatibilität zu KMP migriert. Die KMP-Version von Room unterscheidet sich je nach Plattform und von der Android-spezifischen Version etwas. Diese Unterschiede sind unten aufgeführt und beschrieben.

Blockieren von DAO-Funktionen

Wenn Sie Room für KMP verwenden, müssen alle DAO-Funktionen, die für andere Plattformen als Android kompiliert wurden, suspend-Funktionen sein, mit Ausnahme von reaktiven 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() { // … }
}

Room profitiert von der funktionsreichen asynchronen kotlinx.coroutines-Bibliothek, die Kotlin für mehrere Plattformen anbietet. Für eine optimale Funktionalität werden suspend-Funktionen für DAOs erzwungen, die in einem KMP-Projekt kompiliert wurden, mit Ausnahme von Android-spezifischen DAOs, um die Abwärtskompatibilität mit der vorhandenen Codebasis aufrechtzuerhalten.

Funktionsunterschiede zu KMP

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

@RawQuery-DAO-Funktionen

Für Funktionen, die mit @RawQuery annotiert und für andere Plattformen als Android kompiliert werden, muss anstelle von SupportSQLiteQuery ein Parameter vom Typ RoomRawQuery deklariert werden.

@Dao
interface TodoDao {
  @RawQuery
  suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}

Mit einem RoomRawQuery kann dann eine Abfrage zur Laufzeit erstellt werden:

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

Rückruf bei Abfrage

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

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

In einer zukünftigen Version von Room wird die Unterstützung für Abfrage-Callbacks hinzugefügt.

Die API zum Konfigurieren eines RoomDatabase mit einem Abfrage-CallbackRoomDatabase.Builder.setQueryCallback und die Callback-SchnittstelleRoomDatabase.QueryCallback sind nicht allgemein verfügbar und daher auch nicht auf anderen Plattformen als Android verfügbar.

Automatisch schließende Datenbank

Die API zum Aktivieren des automatischen Schließens nach einem Zeitlimit, RoomDatabase.Builder.setAutoCloseTimeout, ist nur auf Android-Geräten verfügbar.

Datenbank vorbereiten

Die folgenden APIs zum Erstellen einer RoomDatabase mit einer vorhandenen Datenbank (d.h. einer vorkonfigurierten Datenbank) sind nicht allgemein verfügbar und daher auf anderen Plattformen als Android nicht verfügbar. Das sind:

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

In einer zukünftigen Version von Room wird es Unterstützung für vorkonfigurierte Datenbanken geben.

Entwertung mehrerer Instanzen

Die API zur Aktivierung der Invalidation für mehrere Instanzen, RoomDatabase.Builder.enableMultiInstanceInvalidation, ist nur auf Android-Geräten verfügbar und nicht auf gängigen oder anderen Plattformen.