Библиотека сохранения данных Room предоставляет уровень абстракции поверх SQLite, обеспечивая более надёжный доступ к базе данных и используя при этом всю мощь SQLite. Эта страница посвящена использованию Room в проектах Kotlin Multiplatform (KMP) . Подробнее об использовании Room см. в статье «Сохранение данных в локальной базе данных с помощью Room» или в наших официальных примерах .
Настройка зависимостей
Чтобы настроить Room в вашем проекте KMP, добавьте зависимости для артефактов в файле build.gradle.kts
для вашего модуля KMP.
Определите зависимости в файле libs.versions.toml
:
[versions]
room = "2.7.2"
sqlite = "2.5.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
Добавьте плагин Room Gradle для настройки схем Room и плагина KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Добавьте зависимость среды выполнения Room и связанную библиотеку SQLite:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
Добавьте зависимости KSP в блок корневых dependencies
. Обратите внимание, что вам необходимо добавить все целевые объекты, используемые вашим приложением. Подробнее см. в статье KSP with Kotlin Multiplatform .
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Определите каталог схемы Room. Дополнительные сведения см. в разделе «Установка расположения схемы с помощью плагина Room Gradle» .
room {
schemaDirectory("$projectDir/schemas")
}
Определить классы базы данных
Вам необходимо создать класс базы данных с аннотацией @Database
, а также объекты DAO и сущности внутри общего исходного набора вашего общего модуля KMP. Размещение этих классов в общих исходных наборах позволит использовать их на всех целевых платформах.
// 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("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
При объявлении expect
объекта с интерфейсом RoomDatabaseConstructor
компилятор Room генерирует actual
реализации. Android Studio может выдать следующее предупреждение, которое можно отключить с помощью @Suppress("KotlinNoActualForExpect")
:
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
Затем либо определите новый интерфейс DAO , либо переместите существующий в commonMain
:
// shared/src/commonMain/kotlin/TodoDao.kt
@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>>
}
Определите или переместите ваши сущности в commonMain
:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
Создайте конструктор баз данных для конкретной платформы
Вам необходимо определить конструктор баз данных для создания экземпляра Room на каждой платформе. Это единственная часть API, которая должна присутствовать в исходных наборах, специфичных для платформы, из-за различий в API файловых систем.
Андроид
На Android местоположение базы данных обычно определяется через API Context.getDatabasePath()
. Чтобы создать экземпляр базы данных, укажите Context
вместе с путём к ней.
// shared/src/androidMain/kotlin/Database.android.kt
fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
Чтобы создать экземпляр базы данных в iOS, укажите путь к базе данных с помощью NSFileManager
, который обычно находится в NSDocumentDirectory
.
// shared/src/iosMain/kotlin/Database.ios.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.desktop.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
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
Выберите драйвер SQLite
В предыдущем фрагменте кода вызывается функция-конструктор setDriver
, чтобы определить, какой драйвер SQLite должна использовать база данных Room. Эти драйверы различаются в зависимости от целевой платформы. В предыдущих фрагментах кода используется BundledSQLiteDriver
. Это рекомендуемый драйвер, включающий SQLite, скомпилированный из исходного кода, что обеспечивает наиболее согласованную и актуальную версию SQLite для всех платформ.
Если вы хотите использовать SQLite, предоставляемый ОС, используйте API setDriver
в платформенно-зависимых исходных наборах, которые определяют платформенно-зависимый драйвер. Описание доступных реализаций драйверов см. в разделе «Реализации драйверов». Вы можете использовать любой из следующих вариантов:
-
AndroidSQLiteDriver
вandroidMain
-
NativeSQLiteDriver
вiosMain
Чтобы использовать NativeSQLiteDriver
, необходимо указать опцию компоновщика -lsqlite3
, чтобы приложение 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")
}
}
}
Установить контекст сопрограммы (необязательно)
Объект RoomDatabase
на Android может быть дополнительно настроен с использованием общих исполнителей приложений с помощью RoomDatabase.Builder.setQueryExecutor()
для выполнения операций с базой данных.
Поскольку исполнители несовместимы с KMP, API setQueryExecutor()
класса Room недоступен в commonMain
. Вместо этого объект RoomDatabase
должен быть настроен с использованием CoroutineContext
, который можно задать с помощью RoomDatabase.Builder.setCoroutineContext()
. Если контекст не задан, объект RoomDatabase
по умолчанию будет использовать Dispatchers.IO
.
Минификация и обфускация
Если проект минифицирован или обфусцирован, то необходимо включить следующее правило ProGuard, чтобы Room мог найти сгенерированную реализацию определения базы данных:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Переход на Kotlin Multiplatform
Изначально Room разрабатывался как библиотека для Android, а затем был перенесён в KMP с учётом совместимости с API. Версия Room для KMP несколько отличается на разных платформах и от версии для Android. Эти различия перечислены и описаны ниже.
Переход с поддержки SQLite на драйвер SQLite
Любое использование SupportSQLiteDatabase
и других API в androidx.sqlite.db
необходимо реорганизовать с использованием API драйвера SQLite, поскольку API в androidx.sqlite.db
предназначены только для Android (обратите внимание на другой пакет, нежели пакет KMP).
Для обеспечения обратной совместимости, если RoomDatabase
настроена с использованием SupportSQLiteOpenHelper.Factory
(например, не установлен SQLiteDriver
), Room работает в «режиме совместимости», в котором API поддержки SQLite и драйвера SQLite работают корректно. Это позволяет выполнять инкрементальные миграции, избавляя от необходимости конвертировать все используемые версии Support SQLite в драйвер SQLite одним изменением.
Преобразование подклассов миграций
Подклассы миграций необходимо перенести в аналоги драйвера SQLite:
Kotlin Мультиплатформенный
Подклассы миграции
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
Подклассы спецификации автоматической миграции
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
только для Android
Подклассы миграции
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
Подклассы спецификации автоматической миграции
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
Конвертировать обратный вызов базы данных
Обратные вызовы базы данных необходимо перенести в аналоги драйвера SQLite:
Kotlin Мультиплатформенный
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
только для Android
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
Преобразование функций DAO @RawQuery
Функциям, аннотированным @RawQuery
, которые скомпилированы для платформ, отличных от Android, необходимо будет объявить параметр типа RoomRawQuery
вместо SupportSQLiteQuery
.
Kotlin Мультиплатформенный
Определить исходный запрос
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
Затем RoomRawQuery
можно использовать для создания запроса во время выполнения:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?",
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todoDao().getTodos(query)
}
только для Android
Определить исходный запрос
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
Затем SimpleSQLiteQuery
можно использовать для создания запроса во время выполнения:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
Преобразование блокирующих функций DAO
Room использует многофункциональную асинхронную библиотеку kotlinx.coroutines
, которую Kotlin предлагает для различных платформ. Для оптимальной функциональности функции suspend
применяются к объектам DAO, скомпилированным в проекте KMP, за исключением DAO, реализованных в androidMain
для обеспечения обратной совместимости с существующей кодовой базой. При использовании Room для KMP все функции DAO, скомпилированные для платформ, отличных от Android, должны быть функциями suspend
.
Kotlin Мультиплатформенный
Приостановка запросов
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Приостановка транзакций
@Transaction
suspend fun transaction() { … }
только для Android
Блокировка запросов
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Блокировка транзакций
@Transaction
fun blockingTransaction() { … }
Преобразование реактивных типов в Flow
Не все функции DAO должны быть функциями приостановки. Функции DAO, возвращающие реактивные типы, такие как LiveData
или Flowable
из RxJava, не следует преобразовывать в функции приостановки. Однако некоторые типы, например, LiveData
, несовместимы с KMP. Функции DAO с реактивными типами возврата необходимо перенести в потоки сопрограмм.
Kotlin Мультиплатформенный
Реактивные типы Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
только для Android
Реактивные типы, такие как LiveData
или Flowable
от RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
API-интерфейсы конвертации транзакций
API транзакций базы данных для Room KMP могут различать транзакции записи ( useWriterConnection
) и чтения ( useReaderConnection
).
Kotlin Мультиплатформенный
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
только для Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Запись транзакций
Используйте транзакции записи, чтобы гарантировать, что несколько запросов будут записывать данные атомарно, обеспечивая единообразный доступ к ним для читателей. Это можно сделать, используя useWriterConnection
с любым из трёх типов транзакций:
immediateTransaction
: В режиме упреждающей записи (WAL) (по умолчанию) транзакция этого типа блокируется при запуске, но читатели могут продолжать чтение. Это предпочтительный вариант для большинства случаев.deferredTransaction
: транзакция не получит блокировку до первого оператора записи. Используйте этот тип транзакции для оптимизации, когда вы не уверены, потребуется ли операция записи внутри транзакции. Например, если вы запускаете транзакцию для удаления песен из плейлиста, указав только его имя, а плейлист не существует, то операция записи (удаления) не требуется.exclusiveTransaction
: этот режим работает аналогичноimmediateTransaction
в режиме WAL. В других режимах журналирования он запрещает другим подключениям к базе данных читать данные во время выполнения транзакции.
Чтение транзакций
Используйте транзакции чтения для последовательного многократного чтения данных из базы данных. Например, когда у вас два или более отдельных запроса и вы не используете предложение JOIN
. В соединениях чтения разрешены только отложенные транзакции. Попытка запустить немедленную или исключительную транзакцию в соединении чтения приведёт к исключению, поскольку они считаются операциями записи.
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Недоступно в Kotlin Multiplatform
Некоторые API, доступные для Android, недоступны в Kotlin Multiplatform.
Запрос обратного вызова
Следующие 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 и недоступен на распространенных или других платформах.
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Перенос существующих приложений в Room KMP Codelab
- Начните работу с KMP Codelab
- Сохранение данных в локальной базе данных с помощью Room