Библиотека Room для работы с данными предоставляет уровень абстракции над SQLite, обеспечивая более надежный доступ к базе данных и используя все возможности SQLite. Эта страница посвящена использованию Room в проектах Kotlin Multiplatform (KMP) . Для получения дополнительной информации об использовании Room см. раздел «Сохранение данных в локальной базе данных с помощью Room» или наши официальные примеры .
Настройка зависимостей
Для настройки Room в вашем проекте KMP добавьте зависимости для артефактов в файл build.gradle.kts для вашего модуля KMP.
Укажите зависимости в файле libs.versions.toml :
[versions]
room = "2.8.4"
sqlite = "2.6.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 для настройки схем помещений и плагин 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 с 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
На 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 builder для определения того, какой драйвер 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")
}
}
}
Задайте контекст сопрограммы (необязательно)
В Android объект RoomDatabase можно дополнительно настроить с помощью общих исполнителей приложений, используя 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 Support SQLite и SQLite Driver работают должным образом. Это позволяет выполнять поэтапную миграцию, так что вам не нужно будет преобразовывать все ваши использования Support SQLite в SQLite Driver одним изменением.
Используйте SQLite-оболочку для Room (необязательно)
Артефакт androidx.room:room-sqlite-wrapper предоставляет API для связи между SQLiteDriver и SupportSQLiteDatabase во время миграции.
Чтобы получить объект SupportSQLiteDatabase из RoomDatabase , настроенного с использованием SQLiteDriver , используйте новую функцию расширения RoomDatabase.getSupportWrapper() . Эта совместимая обертка помогает поддерживать существующие способы использования SupportSQLiteDatabase (часто получаемые из RoomDatabase.openHelper.writableDatabase ) при переходе на SQLiteDriver , особенно для кодовых баз с обширным использованием API SupportSQLite , которые хотят использовать BundledSQLiteDriver .
Преобразование подклассов миграций
Подклассы миграций необходимо перенести в соответствующие драйверы SQLite:
Kotlin Multiplatform
Подклассы миграции
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 Multiplatform
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 Multiplatform
Определите исходный запрос
@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 functions) обязательны для DAO, скомпилированных в проекте KMP, за исключением DAO, реализованных в androidMain для сохранения обратной совместимости с существующей кодовой базой. При использовании Room для KMP все функции DAO, скомпилированные для платформ, отличных от Android, должны быть функциями suspend .
Kotlin Multiplatform
Приостановка запросов
@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 Multiplatform
Реактивные типы 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 Multiplatform
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.