Pokój (wieloplatformowy Kotlin)

Biblioteka trwałości pomieszczeń zapewnia warstwę abstrakcji w standardzie SQLite, aby umożliwić który zapewnia wydajniejszy dostęp do bazy danych przy jednoczesnym wykorzystaniu wszystkich możliwości SQLite. Ten dotyczy używania pokoju w projektach wieloplatformowych Kotlin (KMP). Więcej Informacje o korzystaniu z Sali znajdziesz w artykule Zapisywanie danych w lokalnej bazie danych przy użyciu Sal lub naszych oficjalnych sampli.

Konfigurowanie zależności

Obecna wersja sali, która obsługuje platformę KMP, to Wersja 2.7.0-alfa01 lub nowsza.

Aby skonfigurować Pokój w projekcie KMP, dodaj zależności między artefaktami w build.gradle.kts plik dla Twojego modułu:

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

Musisz też skonfigurować sterownik SQLite pokoju. Sterowniki różnią się na podstawie platformy docelowej. Zobacz Implementacje sterowników .

Dodatkowe informacje o konfiguracji:

.

Definiowanie klas bazy danych

Musisz utworzyć klasę bazy danych z adnotacjami @Database oraz DAO i encje w wspólnym zbiorze źródłowym udostępnionego modułu KMP. Umieszczanie te klasy we wspólnych źródłach będą mogły być udostępniane wszystkim użytkownikom docelowym platform.

Gdy zadeklarujesz obiekt expect w interfejsie RoomDatabaseConstructor, kompilator sal generuje actual implementacji. Android Studio może wyświetlić ostrzeżenie "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; you can suppress the warning with@Pomijanie("NO_ACTUAL_FOR_EXPECT")`.

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

@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 możesz użyć deklaracji rzeczywistych / oczekujących. aby tworzyć implementacje pokoi na określonych platformach. Na przykład możesz dodać DAO na określonych platformach, zdefiniowany we wspólnym kodzie za pomocą algorytmu expect, a następnie: określ definicje actual z dodatkowymi zapytaniami w kontekście konkretnej platformy. zbiorów źródłowych.

Tworzę konstruktor baz danych

Aby utworzyć instancję pokoju na każdej platformie, musisz zdefiniować kreator baz danych. Ten to jedyna część interfejsu API, która musi znajdować się w źródle na poziomie platformy ze względu na różnice w interfejsach API systemu plików. Na przykład w Androidzie lokalizacja bazy danych jest zwykle uzyskiwana za pomocą Context.getDatabasePath() API, a w systemie iOS lokalizacja bazy danych to uzyskać za pomocą NSFileManager.

Android

Aby utworzyć instancję bazy danych, określ kontekst wraz z bazą danych ścieżki konwersji.

// 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 ścieżkę do bazy danych za pomocą atrybutu NSFileManager, zwykle w regionie NSDocumentDirectory.

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

Aby utworzyć instancję bazy danych, podaj ścieżkę bazy danych za pomocą Javy lub Kotlin API.

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

Tworzenie instancji bazy danych

Po uzyskaniu RoomDatabase.Builder od jednego z użytkowników platformy konstruktorami, możesz skonfigurować resztę bazy danych sal we wspólnym kodzie wraz 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()
}

Wybór SQLiteDriver

Poprzednie fragmenty kodu korzystają z tagu BundledSQLiteDriver. To jest zalecany sterownik zawierający SQLite skompilowany ze źródła, który udostępnia najbardziej spójną i aktualną wersję SQLite na wszystkich platformach. Jeśli Jeśli chcesz korzystać z SQLite dostarczonego przez system operacyjny, użyj interfejsu API setDriver na odpowiedniej platformie zbiorów źródłowych, które określają sterownik dla danej platformy. W przypadku Androida możesz użyć AndroidSQLiteDriver, a iOS – NativeSQLiteDriver. Do używasz NativeSQLiteDriver, musisz udostępnić opcję tagu łączącego, tak aby system iOS aplikacja dynamicznie łączy się z systemową bazą 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 został pierwotnie utworzony jako biblioteka na Androida, a później został przeniesiony do KMP z naciskiem na zgodność z interfejsami API. Wersja pokoju w KMP różni się nieco między platformami i od wersji na Androida. Różnice te są następujące który został opisany poniżej.

Blokowanie funkcji DAO

W przypadku korzystania z usługi Room dla KMP wszystkie funkcje DAO są kompilowane na platformy inne niż Android. muszą być funkcjami suspend z wyjątkiem reaktywnych typów zwrotów, takich jak jako 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() { // … }
}

Dostępne w pokojach funkcje z bogatą w funkcje asynchroniczną biblioteką kotlinx.coroutines które Kotlin oferuje na wiele platform. Aby zapewnić optymalne działanie, suspend funkcje są egzekwowane w przypadku jednostek DAO skompilowanych w projekcie KMP, z wyjątkiem DAO dla Androida, by zachować zgodność wsteczną z istniejącymi bazy kodu.

Różnice funkcjonalne z KMP

Z tej sekcji dowiesz się, czym różnią się funkcje KMP i platformy Androida wersji pokoju.

Funkcje DAO @RawQuery

Funkcje z adnotacjami @RawQuery, które są kompilowane na platformy inne niż Android będzie musiał zadeklarować parametr typu RoomRawQuery, a nie SupportSQLiteQuery

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

Za pomocą RoomRawQuery można następnie utworzyć zapytanie w czasie działania:

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

Zapytanie o wywołanie zwrotne

Poniższe interfejsy API do konfigurowania wywołań zwrotnych zapytań nie są dostępne wspólnie i dlatego są niedostępne na platformach innych niż Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

W przyszłej wersji pokoju planujemy dodać obsługę wywołań zwrotnych zapytań.

Interfejs API do konfigurowania RoomDatabase z wywołaniem zwrotnym zapytania RoomDatabase.Builder.setQueryCallback wraz z interfejsem wywołania zwrotnego Listy RoomDatabase.QueryCallback nie są wspólne, więc są niedostępne na platformach innych niż Android.

Automatyczne zamykanie bazy danych

interfejs API do włączania automatycznego zamykania po upływie limitu czasu, Aplikacja RoomDatabase.Builder.setAutoCloseTimeout jest dostępna tylko na urządzeniach z Androidem i jest niedostępne na innych platformach.

Baza danych przed pakietami

Poniższe interfejsy API pozwalają utworzyć RoomDatabase przy użyciu istniejącej bazy danych (np. gotowa baza danych) nie są wspólne i dlatego nie są dostępne na platformach innych 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. Pokój.

Unieważnienie wielu instancji

interfejs API do włączania unieważniania wielu instancji, Pole RoomDatabase.Builder.enableMultiInstanceInvalidation jest dostępne tylko w tych krajach: Androida i nie jest dostępna na wspólnych platformach.