Библиотека персистентности Room предоставляет уровень абстракции поверх SQLite, чтобы обеспечить более надежный доступ к базе данных, используя при этом всю мощь SQLite. На этой странице основное внимание уделяется использованию Room в многоплатформенных проектах Kotlin (KMP). Дополнительные сведения об использовании Room см. в разделе Сохранение данных в локальной базе данных с помощью Room или наших официальных образцах .
Настройка зависимостей
Текущая версия Room, поддерживающая KMP, — 2.7.0-alpha01 или выше.
Чтобы настроить Room в проекте KMP, добавьте зависимости для артефактов в файл build.gradle.kts
для вашего модуля:
-
androidx.room:room-gradle-plugin
— плагин Gradle для настройки схем комнат. -
androidx.room:room-compiler
— процессор KSP, генерирующий код. -
androidx.room:room-runtime
— исполняющая часть библиотеки. -
androidx.sqlite:sqlite-bundled
— (необязательно) встроенная библиотека SQLite.
Кроме того, вам необходимо настроить драйвер SQLite для Room. Эти драйверы различаются в зависимости от целевой платформы. См. Реализации драйверов для описания доступных реализаций драйверов.
Дополнительную информацию о настройке см. в следующих разделах:
- Установите местоположение схемы с помощью плагина Room Gradle .
- KSP с мультиплатформой Kotlin .
- Добавление зависимостей времени выполнения .
Определение классов базы данных
Вам необходимо создать класс базы данных, аннотированный @Database
, вместе с DAO и объектами внутри общего исходного набора вашего общего модуля KMP. Размещение этих классов в общих источниках позволит использовать их на всех целевых платформах.
Когда вы объявляете expect
объект с помощью интерфейса RoomDatabaseConstructor
, компилятор Room генерирует actual
реализации. Android Studio может выдать предупреждение "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; you can suppress the warning with
@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>
@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
)
Обратите внимание, что вы можете дополнительно использовать фактические/ожидаемые объявления для создания реализаций комнаты для конкретной платформы. Например, вы можете добавить DAO для конкретной платформы, которая определена в общем коде с помощью expect
, а затем указать actual
определения с помощью дополнительных запросов в наборах исходных кодов для конкретной платформы.
Создание построителя базы данных
Вам необходимо определить построитель базы данных для создания экземпляра Room на каждой платформе. Это единственная часть API, которая должна присутствовать в исходных наборах для конкретной платформы из-за различий в API файловых систем. Например, в Android расположение базы данных обычно получается через API Context.getDatabasePath()
, а в iOS расположение базы данных получается с помощью NSFileManager
.
Андроид
Чтобы создать экземпляр базы данных, укажите контекст вместе с путем к базе данных.
// 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
Чтобы создать экземпляр базы данных, укажите путь к базе данных с помощью NSFileManager
, обычно расположенного в 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 (настольный компьютер)
Чтобы создать экземпляр базы данных, укажите путь к базе данных с помощью API Java или 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,
)
}
Создание экземпляра базы данных
Получив RoomDatabase.Builder
от одного из конструкторов, специфичных для платформы, вы можете настроить остальную часть базы данных Room в общем коде вместе с фактическим созданием экземпляра базы данных.
// 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
В предыдущих фрагментах кода используется BundledSQLiteDriver
. Это рекомендуемый драйвер, включающий SQLite, скомпилированный из исходного кода, который обеспечивает наиболее согласованную и актуальную версию SQLite на всех платформах. Если вы хотите использовать SQLite, предоставляемый ОС, используйте API setDriver
в исходных наборах для конкретной платформы, в которых указан драйвер для конкретной платформы. Для Android вы можете использовать AndroidSQLiteDriver
, а для iOS — NativeSQLiteDriver
. Чтобы использовать NativeSQLiteDriver
, вам необходимо предоставить опцию компоновщика, чтобы приложение iOS динамически связывалось с системным 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")
}
}
}
Различия
Изначально Room разрабатывался как библиотека Android, а затем был перенесен на KMP с упором на совместимость API. Версия Room KMP несколько отличается на разных платформах и от версии для Android. Эти различия перечислены и описаны следующим образом.
Блокировка функций DAO
При использовании Room для KMP все функции DAO, скомпилированные для платформ, отличных от Android, должны быть suspend
функциями, за исключением реактивных типов возврата, таких как 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 использует многофункциональную асинхронную библиотеку kotlinx.coroutines
, которую Kotlin предлагает для нескольких платформ. Для оптимальной функциональности функции suspend
применяются для DAO, скомпилированных в проекте KMP, за исключением DAO, специфичных для Android, для обеспечения обратной совместимости с существующей кодовой базой.
Отличия функций от KMP
В этом разделе описывается, чем отличаются функции версий Room для KMP и Android.
@RawQuery функции DAO
Функции, аннотированные @RawQuery
и скомпилированные для платформ, отличных от Android, должны будут объявить параметр типа RoomRawQuery
вместо SupportSQLiteQuery
.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
Затем RoomRawQuery
можно использовать для создания запроса во время выполнения:
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)
}
Запрос обратного вызова
Следующие API для настройки обратных вызовов запросов не являются общедоступными и поэтому недоступны на платформах, отличных от Android.
-
RoomDatabase.Builder.setQueryCallback
-
RoomDatabase.QueryCallback
Мы намерены добавить поддержку обратного вызова запроса в будущей версии Room.
API для настройки RoomDatabase
с обратным вызовом запроса RoomDatabase.Builder.setQueryCallback
вместе с интерфейсом обратного вызова RoomDatabase.QueryCallback
обычно не доступны и, следовательно, недоступны на других платформах, кроме Android.
Автоматическое закрытие базы данных
API для включения автоматического закрытия после тайм-аута RoomDatabase.Builder.setAutoCloseTimeout
доступен только на Android и недоступен на других платформах.
База данных предварительных пакетов
Следующие API-интерфейсы для создания RoomDatabase
с использованием существующей базы данных (т. е. предварительно упакованной базы данных) недоступны обычно и, следовательно, недоступны на других платформах, кроме Android. Эти API:
-
RoomDatabase.Builder.createFromAsset
-
RoomDatabase.Builder.createFromFile
-
RoomDatabase.Builder.createFromInputStream
-
RoomDatabase.PrepackagedDatabaseCallback
Мы намерены добавить поддержку предварительно упакованных баз данных в будущей версии Room.
Недействительность нескольких экземпляров
API для включения недействительности нескольких экземпляров RoomDatabase.Builder.enableMultiInstanceInvalidation
доступен только на Android и недоступен на обычных или других платформах.