Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、データベースへのより安定したアクセスを可能にし、SQLite を最大限に活用できるようにします。このページでは、Kotlin マルチプラットフォーム(KMP)プロジェクトでの Room の使用について説明します。Room の使用方法について詳しくは、Room を使用してローカル データベースにデータを保存するまたは公式サンプルをご覧ください。
依存関係をセットアップする
KMP をサポートする Room の現在のバージョンは 2.7.0-alpha01 以降です。
KMP プロジェクトで Room を設定するには、モジュールの build.gradle.kts
ファイルにアーティファクトの依存関係を追加します。
androidx.room:room-gradle-plugin
- Room スキーマを構成する Gradle プラグインandroidx.room:room-compiler
- コードを生成する KSP プロセッサandroidx.room:room-runtime
- ライブラリのランタイム部分androidx.sqlite:sqlite-bundled
- (省略可)バンドルされた SQLite ライブラリ
また、Room の SQLite ドライバを構成する必要があります。これらのドライバは、ターゲット プラットフォームによって異なります。使用可能なドライバ実装の説明については、ドライバの実装をご覧ください。
設定の詳細については、以下をご覧ください。
データベース クラスの定義
共有 KMP モジュールの共通ソースセット内に、@Database
アノテーション付きのデータベース クラス、DAO、エンティティを作成する必要があります。これらのクラスを共通ソースに配置すると、すべてのターゲット プラットフォームで共有できます。
インターフェース RoomDatabaseConstructor
を使用して expect
オブジェクトを宣言すると、Room コンパイラは actual
実装を生成します。Android Studio から警告 "Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
が出される場合がありますが、@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
)
必要に応じて、actual / expect 宣言を使用して、プラットフォーム固有の Room 実装を作成できます。たとえば、expect
を使用して共通コードで定義されたプラットフォーム固有の DAO を追加し、プラットフォーム固有のソースセットで追加のクエリを使用して actual
定義を指定できます。
データベース ビルダーを作成する
各プラットフォームで Room をインスタンス化するためのデータベース ビルダーを定義する必要があります。これは、ファイル システム API の違いにより、プラットフォーム固有のソースセットに含める必要がある API の唯一の部分です。たとえば、Android では通常、データベースの場所は Context.getDatabasePath()
API を介して取得されますが、iOS では NSFileManager
を使用して取得されます。
Android
データベース インスタンスを作成するには、データベース パスとともにコンテキストを指定します。
// 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(パソコン)
データベース インスタンスを作成するには、Java または 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,
)
}
圧縮と難読化
プロジェクトが圧縮または難読化されている場合は、Room が生成されたデータベース定義の実装を見つけられるように、次の ProGuard ルールを含める必要があります。
-keep class * extends androidx.room.RoomDatabase { <init>(); }
データベースのインスタンス化
プラットフォーム固有のコンストラクタのいずれかから 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 を提供します。OS 提供の SQLite を使用する場合は、プラットフォーム固有のドライバを指定するプラットフォーム固有のソースセットで setDriver
API を使用します。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 ライブラリとして開発されましたが、後に API の互換性に重点を置いて KMP に移行されました。Room の KMP バージョンは、プラットフォームや Android 固有のバージョンとは若干異なります。これらの違いは次のとおりです。
ブロッキング DAO 関数
Room for KMP を使用する場合、Android 以外のプラットフォーム用にコンパイルされたすべての DAO 関数は、Flow
などのリアクティブな戻り値の型を除き、suspend
関数にする必要があります。
// 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 は、Kotlin が複数のプラットフォームで提供する機能豊富な非同期 kotlinx.coroutines
ライブラリを利用しています。最適な機能のために、既存のコードベースとの下位互換性を維持する Android 固有の DAO を除き、KMP プロジェクトでコンパイルされた DAO に suspend
関数が適用されます。
KMP との機能の違い
このセクションでは、Room の KMP バージョンと Android プラットフォーム バージョンの機能の違いについて説明します。
@RawQuery DAO 関数
Android 以外のプラットフォーム用にコンパイルされた @RawQuery
アノテーション付きの関数では、SupportSQLiteQuery
ではなく RoomRawQuery
型のパラメータを宣言する必要があります。
@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 のバージョンでクエリ コールバックのサポートを追加する予定です。
クエリ コールバック RoomDatabase.Builder.setQueryCallback
とコールバック インターフェース RoomDatabase.QueryCallback
を使用して RoomDatabase
を構成する API は一般に利用できないため、Android 以外のプラットフォームでは使用できません。
自動クローズ データベース
タイムアウト後に自動クローズを有効にする API(RoomDatabase.Builder.setAutoCloseTimeout
)は Android でのみ使用でき、他のプラットフォームでは使用できません。
事前パッケージ化されたデータベース
既存のデータベース(事前パッケージ化されたデータベース)を使用して RoomDatabase
を作成する次の API は、一般に使用できないため、Android 以外のプラットフォームでは使用できません。これらの API は次のとおりです。
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
今後の Room のバージョンで、事前パッケージ化されたデータベースのサポートを追加する予定です。
マルチインスタンスの無効化
マルチインスタンスの無効化を有効にする API である RoomDatabase.Builder.enableMultiInstanceInvalidation
は Android でのみ使用でき、一般的なプラットフォームや他のプラットフォームでは使用できません。