Pokój (wieloplatformowy Kotlin)

Biblioteka trwałości pokoju udostępnia warstwę abstrakcji nad bazą danych SQLite, która zapewnia bardziej solidny dostęp do bazy danych przy jednoczesnym wykorzystaniu wszystkich możliwości SQLite. Ta strona dotyczy korzystania z usługi Room w projektach Kotlin Multiplatform (KMP). Więcej informacji o korzystaniu z informacji o sali znajdziesz w artykule o zapisywaniu danych w lokalnej bazie danych za pomocą funkcji Room lub w naszych oficjalnych przykładach.

Konfigurowanie zależności

Obecna wersja Pokoju, która obsługuje KMP, to 2.7.0-alpha01 lub nowsza.

Aby skonfigurować Room w projekcie KMP, dodaj zależności artefaktów w pliku build.gradle.kts modułu:

  • androidx.room:room-gradle-plugin – wtyczka Gradle do konfigurowania schematów sal
  • androidx.room:room-compiler – procesor KSP, który generuje kod
  • androidx.room:room-runtime – część biblioteki środowiska wykonawczego.
  • androidx.sqlite:sqlite-bundled – (opcjonalnie) dołączona biblioteka SQLite

Dodatkowo musisz skonfigurować sterownik SQLite dla sali. Czynniki te różnią się w zależności od platformy docelowej. W sekcji Implementacje sterowników znajdziesz opisy dostępnych implementacji sterownika.

Dodatkowe informacje o konfiguracji znajdziesz w tych artykułach:

Definiowanie klas bazy danych

Musisz utworzyć klasę bazy danych z adnotacją @Database wraz z DAO i encjami wewnątrz wspólnego zestawu źródłowego współdzielonego modułu KMP. Umieszczenie tych klas we wspólnych źródłach umożliwi ich udostępnianie na wszystkich platformach docelowych.

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

Pamiętaj, że do tworzenia implementacji sal na podstawie platformy możesz używać deklaracji rzeczywistych / oczekiwanych. Możesz np. dodać specyficzną dla platformy DAO definiowaną we wspólnym kodzie za pomocą expect, a potem określić definicje actual za pomocą dodatkowych zapytań w zbiorach źródłowych powiązanych z daną platformą.

Tworzenie konstruktora baz danych

Aby utworzyć instancję Room na każdej platformie, musisz zdefiniować konstruktora baz danych. Jest to jedyna część interfejsu API, która musi znajdować się w zbiorach źródłowych na potrzeby danej platformy z powodu różnic między interfejsami API systemu plików. Na przykład na Androidzie lokalizację bazy danych uzyskuje się zwykle za pomocą interfejsu API Context.getDatabasePath(), a na iOS – za pomocą metody NSHomeDirectory.

Android

Aby utworzyć instancję bazy danych, podaj Kontekst wraz ze ścieżką bazy danych. Nie musisz określać fabryki baz danych.

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

Aby utworzyć instancję bazy danych, podaj fabrykę bazy danych wraz ze ścieżką bazy danych. Fabryka bazy danych to funkcja lambda, która wywołuje wygenerowaną funkcję rozszerzenia o nazwie instantiateImpl z odbiornikiem typu KClass<T>, gdzie T to typ klasy @Database z adnotacjami.

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

Aby utworzyć instancję bazy danych, podaj tylko ścieżkę bazy danych. Nie musisz tworzyć fabryki baz danych.

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

Tworzenie instancji bazy danych

Gdy uzyskasz RoomDatabase.Builder z jednego z konstruktora bazy danych na danej platformie, możesz skonfigurować resztę bazy danych Room we wspólnym kodzie razem z rzeczywistym wystąpieniem bazy danych.

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

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

Wybieranie sterownika SQLiteDriver

Poprzednie fragmenty kodu używają BundledSQLiteDriver. Jest to zalecany sterownik zawierający skompilowany ze źródła kod SQLite, który zapewnia najbardziej spójną i aktualną wersję SQLite na wszystkich platformach. Jeśli chcesz korzystać z serwera SQLite dostarczonego przez system operacyjny, użyj interfejsu API setDriver w zbiorach źródłowych powiązanych z konkretną platformą, które określają sterownik na danej platformie. Na Androidzie możesz używać aplikacji AndroidSQLiteDriver, a na iOS – NativeSQLiteDriver. Aby używać NativeSQLiteDriver, musisz udostępnić opcję tagu łączącego, tak aby aplikacja na iOS dynamicznie łączyła się z systemowym SQLite.

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

Różnice

Pokój był pierwotnie biblioteką Androida, a później został przeniesiony do KMP ze względu na zgodność z interfejsami API. Wersja KMP pomieszczenia różni się w zależności od platformy oraz od wersji specyficznej dla Androida. Różnice te są wymienione i opisane poniżej.

Blokowanie funkcji DAO

Jeśli używasz pomieszczenia w KMP, wszystkie funkcje DAO skompilowane na platformy inne niż Android muszą być funkcjami suspend z wyjątkiem reaktywnych typów zwrotów, takich jak 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() { // … }
}

Kotlin udostępnia wiele funkcji w ramach asynchronicznej biblioteki kotlinx.coroutines z wieloma funkcjami, którą Kotlin oferuje na różnych platformach. Aby zapewnić optymalną funkcjonalność, w przypadku DAO skompilowanych w projekcie KMP funkcje suspend są egzekwowane z wyjątkiem DAO specyficznych dla Androida, co pozwala zachować wsteczną zgodność z istniejącą bazą kodu.

Różnice w funkcjach w stosunku do KMP

W tej sekcji omawiamy różnice między funkcjami w wersjach KMP a wersji Room Rooma na Androida.

Funkcje DAO w @RawQuery

Funkcje z adnotacjami @RawQuery skompilowane na platformy inne niż Android będą powodować błąd. W przyszłości zamierzamy dodać obsługę @RawQuery do Pokoju.

Zapytanie zwrotne

Podane niżej interfejsy API do konfigurowania wywołań zwrotnych zapytań nie są wspólne i dlatego są niedostępne na platformach innych niż Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

W przyszłości planujemy dodać obsługę wywołania zwrotnego zapytania w przyszłej wersji pokoju.

Interfejs API służący do konfigurowania RoomDatabase z wywołaniem zwrotnym RoomDatabase.Builder.setQueryCallback i interfejsem wywołania zwrotnego RoomDatabase.QueryCallback nie jest wspólny, więc nie jest dostępny na innych platformach niż Android.

Automatyczne zamykanie bazy danych

Interfejs API umożliwiający automatyczne zamykanie po upływie limitu czasu (RoomDatabase.Builder.setAutoCloseTimeout) jest dostępny tylko na urządzeniach z Androidem i nie jest dostępny na innych platformach.

Baza danych sprzed pakietu

Poniższe interfejsy API do tworzenia RoomDatabase przy użyciu istniejącej bazy danych (np. bazy danych wstępnie spakowanej) nie są wspólne, dlatego nie są dostępne na innych platformach niż Android. Te interfejsy API to:

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

W przyszłości planujemy dodać obsługę gotowych baz danych w pakiecie.

Unieważnienie wielu instancji

Interfejs API umożliwiający unieważnianie wieloinstancji (RoomDatabase.Builder.enableMultiInstanceInvalidation) jest dostępny tylko na Androidzie i nie jest dostępny na popularnych platformach ani na innych platformach.