Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、より強固なデータベース アクセスを実現し、SQLite を最大限に活用できるようにします。このページでは、Kotlin マルチプラットフォーム(KMP)プロジェクトで Room を使用する方法について説明します。Room の使用方法について詳しくは、Room を使用してローカル データベースにデータを保存するまたは公式サンプルをご覧ください。
依存関係を設定する
KMP プロジェクトで Room をセットアップするには、KMP モジュールの build.gradle.kts ファイルにアーティファクトの依存関係を追加します。
libs.versions.toml ファイルで依存関係を定義します。
[versions]
room3 = "3.1.0-alpha03"
sqlite = "2.7.1"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
# Optional SQLite Wrapper
androidx-room3-sqlite-wrapper = { module = "androidx.room3:room3-sqlite-wrapper", version.ref = "room3" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room3 = { id = "androidx.room3", version.ref = "room3" }
Room Gradle プラグインを追加して、Room スキーマと KSP プラグインを構成します。
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room3)
}
Room ランタイムの依存関係とバンドルされた SQLite ライブラリを追加します。
commonMain.dependencies {
implementation(libs.androidx.room3.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room3.sqlite.wrapper)
}
KSP の依存関係をルートの dependencies ブロックに追加します。アプリで使用するすべてのターゲットを追加する必要があります。詳細については、Kotlin マルチプラットフォームでの KSP をご覧ください。
dependencies {
add("kspAndroid", libs.androidx.room3.compiler)
add("kspIosSimulatorArm64", libs.androidx.room3.compiler)
add("kspIosX64", libs.androidx.room3.compiler)
add("kspIosArm64", libs.androidx.room3.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Room スキーマ ディレクトリを定義します。詳細については、Room Gradle プラグインを使用してスキーマの場所を設定するをご覧ください。
room {
schemaDirectory("$projectDir/schemas")
}
データベース クラスを定義する
共有 KMP モジュールの共通ソースセット内に、DAO とエンティティとともに、@Database アノテーションを付けたデータベース クラスを作成する必要があります。これらのクラスを共通ソースに配置すると、すべてのターゲット プラットフォームで共有できるようになります。
// 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 }
RoomDatabaseConstructor インターフェースを使用して expect オブジェクトを宣言すると、Room コンパイラによって actual 実装が生成されます。Android Studio で次の警告が表示されることがあります。この警告は @Suppress("KotlinNoActualForExpect") で抑制できます。
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
次に、新しい DAO インターフェースを定義するか、既存の 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 では、通常、Context.getDatabasePath API を使用してデータベースの場所を取得します。データベース インスタンスを作成するには、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, ) } @OptIn(ExperimentalForeignApi::class) 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.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 ビルダー関数を呼び出して、Room データベースで使用する SQLite ドライバを定義します。これらのドライバは、ターゲット プラットフォームによって異なります。上記のコード スニペットでは BundledSQLiteDriver を使用しています。これは、ソースからコンパイルされた SQLite を含む推奨ドライバであり、すべてのプラットフォームで最も一貫性のある最新バージョンの SQLite を提供します。
OS 提供の SQLite を使用する場合は、プラットフォーム固有のドライバを指定するプラットフォーム固有のソースセットで setDriver API を使用します。利用可能なドライバ実装の説明については、ドライバ実装をご覧ください。次のいずれかを使用できます。
androidMainのAndroidSQLiteDriveriosMainのNativeSQLiteDriver
NativeSQLiteDriver を使用するには、iOS アプリがシステムの SQLite と動的にリンクするように、リンカー オプション -lsqlite3 を指定する必要があります。
// 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 と互換性がないため、Room の setQueryExecutor API は commonMain では使用できません。代わりに、RoomDatabase オブジェクトを CoroutineContext で構成する必要があります。これは RoomDatabase.Builder.setCoroutineContext を使用して設定できます。コンテキストを設定しない場合、RoomDatabase オブジェクトはデフォルトで Dispatchers.IO になります。
軽量化と難読化
プロジェクトが圧縮または難読化されている場合は、Room がデータベース定義の生成された実装を検出できるように、次の ProGuard ルールを含める必要があります。
-keep class * extends androidx.room3.RoomDatabase { <init>(); }
Kotlin Multiplatform に移行する
Room はもともと Android ライブラリとして開発され、その後、API の互換性を重視して KMP に移行されました。Room の KMP バージョンは、プラットフォーム間、および Android 固有のバージョンとは多少異なります。これらの違いは次のとおりです。
Support SQLite から SQLite ドライバに移行する
androidx.sqlite.db の SupportSQLiteDatabase などの API は、androidx.sqlite.db の API が Android 専用であるため、SQLite Driver API でリファクタリングする必要があります(KMP パッケージとは異なるパッケージであることに注意してください)。
下位互換性を確保するため、RoomDatabase が SupportSQLiteOpenHelper.Factory で構成されている限り(たとえば、SQLiteDriver が設定されていない場合)、Room は「互換モード」で動作し、Support SQLite API と SQLite Driver API の両方が想定どおりに動作します。これにより、増分移行が可能になるため、1 回の変更ですべての Support SQLite の使用を SQLite ドライバに変換する必要がなくなります。
Room SQLite ラッパーを使用する
androidx.room3:room3-sqlite-wrapper アーティファクトは、移行中に SQLiteDriver と SupportSQLiteDatabase の間の橋渡しをする API を提供します。
SQLiteDriver で構成された RoomDatabase から SupportSQLiteDatabase を取得するには、新しい拡張関数 RoomDatabase.getSupportWrapper を使用します。この互換性ラッパーは、SQLiteDriver を採用しながら、RoomDatabase.openHelper.writableDatabase から取得することが多い SupportSQLiteDatabase の既存の使用方法を維持するのに役立ちます。特に、SupportSQLite API を広範に使用するコードベースで BundledSQLiteDriver を使用する場合に役立ちます。
移行サブクラスを変換する
移行サブクラスは、対応する SQLite ドライバに移行する必要があります。
Kotlin Multiplatform
移行サブクラス
object Migration_1_2 : Migration(1, 2) { override suspend fun migrate(connection: SQLiteConnection) { // ... } }
自動移行仕様のサブクラス
class AutoMigrationSpec_1_2 : AutoMigrationSpec { override suspend 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 suspend fun onCreate(connection: SQLiteConnection) { // ... } override suspend fun onDestructiveMigration(connection: SQLiteConnection) { // ... } override suspend fun onOpen(connection: SQLiteConnection) { // ... } }
Android のみ
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
@RawQuery DAO 関数を変換する
Android プラットフォーム以外のプラットフォーム用にコンパイルされた @RawQuery アノテーション付きの関数は、SupportSQLiteQuery ではなく RoomRawQuery 型のパラメータを宣言する必要があります。
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 getDao().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 は、Kotlin が複数のプラットフォーム向けに提供している、機能豊富な非同期 kotlinx.coroutines ライブラリのメリットを享受しています。最適な機能を実現するため、KMP プロジェクトでコンパイルされた DAO には suspend 関数が適用されます。ただし、既存のコードベースとの下位互換性を維持するために androidMain で実装された DAO は例外です。KMP で Room を使用する場合、Android プラットフォーム以外のプラットフォーム用にコンパイルされたすべての DAO 関数は 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 関数を suspend 関数にする必要はありません。LiveData や RxJava の Flowable などのリアクティブ型を返す DAO 関数は、suspend 関数に変換しないでください。ただし、LiveData などの一部の型は KMP と互換性がありません。リアクティブな戻り値の型を持つ DAO 関数は、コルーチン フローに移行する必要があります。
Kotlin Multiplatform
リアクティブ型 Flows
@Query("SELECT * FROM Todo") fun getTodosFlow(): Flow<List<Todo>>
Android のみ
LiveData や RxJava の Flowable などのリアクティブ型
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
トランザクション API を変換する
Room KMP のデータベース トランザクション API は、書き込み(useWriterConnection)トランザクションと読み取り(useReaderConnection)トランザクションを区別できます。
Kotlin Multiplatform
val database: RoomDatabase = getRoomDatabase(builder) database.useWriterConnection { transactor -> transactor.immediateTransaction { // perform database operations in transaction. } }
Android のみ
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
書き込みトランザクション
書き込みトランザクションを使用して、複数のクエリがデータをアトミックに書き込むようにすることで、リーダーがデータに一貫してアクセスできるようにします。これを行うには、3 つのトランザクション タイプのいずれかで useWriterConnection を使用します。
immediateTransaction: write-ahead log 書き込み(WAL)モード(デフォルト)では、このタイプのトランザクションは開始時にロックを取得しますが、リーダーは読み取りを続行できます。ほとんどの場合、この方法が推奨されます。deferredTransaction: トランザクションは、最初の書き込みステートメントまでロックを取得しません。トランザクション内で書き込みオペレーションが必要かどうかが不明な場合は、最適化としてこのタイプのトランザクションを使用します。たとえば、プレイリストの名前だけを指定してプレイリストから曲を削除するトランザクションを開始し、そのプレイリストが存在しない場合、書き込み(削除)オペレーションは必要ありません。exclusiveTransaction: このモードは、WAL モードのimmediateTransactionと同じように動作します。他のジャーナリング モードでは、トランザクションの進行中に他のデータベース接続がデータベースを読み取らないようにします。
読み取りトランザクション
読み取りトランザクションを使用して、データベースから複数回一貫して読み取ります。たとえば、2 つ以上の個別のクエリがあり、JOIN 句を使用しない場合です。リーダー接続では、遅延トランザクションのみが許可されます。リーダー接続で即時トランザクションまたは排他的トランザクションを開始しようとすると、これらは「書き込み」オペレーションと見なされるため、例外がスローされます。
val database: RoomDatabase = getRoomDatabase(builder) database.useReaderConnection { transactor -> transactor.deferredTransaction { // perform database operations in transaction. } }
Kotlin マルチプラットフォームでは利用不可
Android で利用可能だった API の一部は、Kotlin マルチプラットフォームでは利用できません。
クエリ コールバック
クエリ コールバックを構成するための次の API は、共通では使用できないため、Android 以外のプラットフォームでは使用できません。
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
データベースの自動クローズ
タイムアウト後に自動的に閉じるように設定する API(RoomDatabase.Builder.setAutoCloseTimeout)は、Android でのみ使用できます。
事前パッケージ化されたデータベース
既存のデータベース(パッケージ化済みのデータベース)を使用して RoomDatabase を作成する次の API は、共通では使用できないため、Android 以外のプラットフォームでは使用できません。
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
マルチインスタンスの無効化
マルチインスタンスの無効化を有効にする API RoomDatabase.Builder.enableMultiInstanceInvalidation は、Android でのみ使用でき、共通プラットフォームや他のプラットフォームでは使用できません。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- Room KMP に既存のアプリを移行する Codelab
- KMP のスタートガイドの Codelab
- Room を使用してローカル データベースにデータを保存する