Biblioteka trwałości Room zapewnia warstwę abstrakcji nad SQLite, aby umożliwić bardziej niezawodny dostęp do bazy danych, jednocześnie wykorzystując pełną moc SQLite. Ta strona dotyczy korzystania z Room w projektach Kotlin Multiplatform (KMP). Więcej informacji o używaniu Room znajdziesz w artykule Zapisywanie danych w lokalnej bazie danych za pomocą Room lub w naszych oficjalnych przykładach.
Konfigurowanie zależności
Obecna wersja Room, 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
swojego modułu:
androidx.room:room-gradle-plugin
– wtyczka Gradle do konfigurowania schematów Roomandroidx.room:room-compiler
– procesor KSP, który generuje kod.androidx.room:room-runtime
– część biblioteki działająca w czasie wykonywania,androidx.sqlite:sqlite-bundled
– (opcjonalnie) biblioteka SQLite dołączona do pakietu
Dodatkowo musisz skonfigurować sterownik SQLite w Room. Te sterowniki różnią się w zależności od platformy docelowej. Opisy dostępnych implementacji sterowników znajdziesz w sekcji Implementacje sterowników.
Więcej informacji o konfigurowaniu znajdziesz w tych artykułach:
- Ustaw lokalizację schematu za pomocą wtyczki Gradle Room.
- Kluczowe cechy produktu z Kotlin Multiplatform
- Dodawanie zależności w czasie działania.
Definiowanie klas bazy danych
Musisz utworzyć klasę bazy danych z adnotacjami @Database
, a także obiekty DAO i encje w ramach wspólnego zestawu źródeł w udostępnionym module KMP. Umieszczenie tych zajęć w powszechnie dostępnych źródłach umożliwi ich udostępnianie na wszystkich platformach docelowych.
Gdy zadeklarujesz obiekt expect
za pomocą interfejsu RoomDatabaseConstructor
, kompilator Room wygeneruje implementacje actual
. Android Studio może wyświetlić ostrzeżenie
"Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
; możesz je zignorować, używając @Suppress("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> {
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
)
Pamiętaj, że możesz opcjonalnie użyć deklaracji actual / expect, aby utworzyć implementacje Room na konkretne platformy. Możesz na przykład dodać DAO dla konkretnej platformy zdefiniowane w kodzie wspólnym za pomocą funkcji expect
, a potem określić definicje actual
za pomocą dodatkowych zapytań w zbiorach źródeł dla poszczególnych platform.
Tworzenie kreatora bazy danych
Aby utworzyć instancję Room na każdej platformie, musisz zdefiniować kreatora bazy danych. To jedyna część interfejsu API, która musi znajdować się w zbiorach źródeł specyficznych dla platformy z powodu różnic w interfejsach API systemu plików. Na przykład w przypadku Androida lokalizacja bazy danych jest zwykle uzyskiwana za pomocą interfejsu API Context.getDatabasePath()
, a w przypadku iOS – za pomocą interfejsu NSFileManager
.
Android
Aby utworzyć instancję bazy danych, podaj kontekst oraz ścieżkę do bazy 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 ścieżkę do bazy danych za pomocą parametru NSFileManager
, który zwykle znajduje się w katalogu 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 (komputera stacjonarnego)
Aby utworzyć instancję bazy danych, podaj ścieżkę do bazy danych za pomocą interfejsów API Java lub Kotlin.
// 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,
)
}
Minifikacja i zaciemnianie
Jeśli projekt jest skompresowany lub zaciemniony, musisz uwzględnić tę regułę Proguard, aby Room mógł znaleźć wygenerowaną implementację definicji bazy danych:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Tworzenie bazy danych
Po uzyskaniu obiektu RoomDatabase.Builder
z jednego z konstruktorów specyficznych dla platformy możesz skonfigurować pozostałą część bazy danych Room w kodzie wspólnym wraz z rzeczywistą instancją 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 obiektu SQLiteDriver
Poprzednie fragmenty kodu używają funkcji BundledSQLiteDriver
. Jest to zalecany sterownik, który zawiera skompilowaną z źródła bazę danych SQLite. Zapewnia ona najbardziej spójną i aktualną wersję SQLite na wszystkich platformach. Jeśli chcesz używać SQLite udostępnianej przez system operacyjny, użyj interfejsu API setDriver
w zbiorach źródeł przeznaczonych dla konkretnej platformy, które określają sterownik dla konkretnej platformy. W przypadku Androida możesz użyć AndroidSQLiteDriver
, a w przypadku iOS – NativeSQLiteDriver
. Aby używać NativeSQLiteDriver
, musisz podać opcję linkera, aby aplikacja na iOS mogła dynamicznie łączyć się z systemem 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
Biblioteka Room została pierwotnie opracowana jako biblioteka na Androida, a później przeniesiona do KMP z naciskiem na zgodność z interfejsem API. Wersja Room na KMP różni się nieco w zależności od platformy i od wersji na Androida. Te różnice zostały wymienione i opisane poniżej.
Blokowanie funkcji DAO
Jeśli używasz Room w KMP, wszystkie funkcje DAO skompilowane na platformy inne niż Android muszą być funkcjami suspend
z wyjątkiem reaktywnej funkcji zwracającej, takiej 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() { // … }
}
Room korzysta z bogatej biblioteki asynchronicznej kotlinx.coroutines
, którą Kotlin udostępnia na wiele platform. Aby zapewnić optymalną funkcjonalność, funkcje suspend
są wymuszane w przypadku DAO skompilowanych w projekcie KMP, z wyjątkiem DAO przeznaczonych do Androida, co ma na celu zachowanie zgodności wstecznej z dotychczasową bazą kodu.
Różnice w funkcjach w porównaniu z kampaniami z dodatkowym płatnym ruchem
W tej sekcji opisano różnice między wersjami aplikacji Room na platformy KMP i Android.
Funkcje DAO @RawQuery
Funkcje z adnotacjami @RawQuery
skompilowane na platformy inne niż Android muszą deklarować parametr typu RoomRawQuery
zamiast SupportSQLiteQuery
.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
Następnie możesz użyć RoomRawQuery
do utworzenia zapytania w czasie wykonywania:
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 połączenie zwrotne
Te interfejsy API do konfigurowania wywołań zwrotnych zapytań nie są dostępne w ogóle, dlatego nie są dostępne na platformach innych niż Android.
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
W przyszłej wersji Room zamierzamy dodać obsługę wywołania zwrotnego zapytania.
Interfejs API do konfigurowania RoomDatabase
z wywołaniem zwrotnym zapytania RoomDatabase.Builder.setQueryCallback
oraz interfejs wywołania zwrotnego RoomDatabase.QueryCallback
nie są dostępne na innych platformach niż Android.
Automatycznie zamykająca się baza danych
Interfejs API umożliwiający automatyczne zamykanie po upływie limitu czasu,
RoomDatabase.Builder.setAutoCloseTimeout
, jest dostępny tylko na Androidzie i nie jest dostępny na innych platformach.
Baza danych w pakiecie
Te interfejsy API do tworzenia RoomDatabase
za pomocą istniejącej bazy danych (czyli skompilowanej bazy danych) nie są dostępne na innych platformach niż Android. Te interfejsy API:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
W przyszłej wersji usługi pokoju zamierzamy dodać obsługę gotowych baz danych.
Unieważnianie wielu instancji
Interfejs API umożliwiający unieważnienie wielu instancjiRoomDatabase.Builder.enableMultiInstanceInvalidation
jest dostępny tylko na Androidzie i nie jest dostępny na innych platformach.