Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、データベースへのより安定したアクセスを可能にし、SQLite を最大限に活用できるようにします。このページでは、Kotlin Multiplatform(KMP) プロジェクトで Room を使用する方法について説明します。Room の使用方法について詳しくは、Room を使用してローカル データベースにデータを保存するまたは 公式サンプルをご覧ください。
依存関係を設定する
KMP プロジェクトで Room を設定するには、KMP モジュールの build.gradle.kts ファイルにアーティファクトの依存関係を追加します。
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 プラグインを追加して、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 ブロックに追加します。アプリで使用するターゲットをすべて追加する必要があります。詳しくは、Kotlin Multiplatform で
KSP を使用するをご覧ください。
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")
}
データベース クラスを定義する
共有 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 インターフェース を定義するか、既存のインターフェースを
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,
)
}
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 を使用するには、リンカー オプション -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 と互換性がないため、Room の setQueryExecutor() API は commonMain で使用できません。代わりに、RoomDatabase オブジェクトを
CoroutineContext で構成する必要があります。これは、
RoomDatabase.Builder.setCoroutineContext() を使用して設定できます。コンテキストが設定されていない場合、RoomDatabase オブジェクトはデフォルトで Dispatchers.IO を使用します。
軽量化と難読化
プロジェクトが軽量化または難読化されている場合は、Room がデータベース定義の生成された実装を見つけられるように、次の ProGuard ルールを含める必要があります。
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Kotlin Multiplatform に移行する
Room は元々 Android ライブラリとして開発され、その後 API 互換性を重視して KMP に移行されました。Room の KMP バージョンは、プラットフォームによって、また Android 固有のバージョンとは若干異なります。これらの違いを以下に示します。
Support SQLite から SQLite ドライバに移行する
androidx.sqlite.db の API は Android 専用であるため(KMP パッケージとは異なるパッケージであることに注意してください)、SupportSQLiteDatabase やその他の API の使用箇所は、SQLite ドライバ API でリファクタリングする必要があります。androidx.sqlite.db
下位互換性のため、RoomDatabase が SupportSQLiteOpenHelper.Factory で構成されている限り(たとえば、SQLiteDriver が設定されていない場合)、Room は「互換モード」で動作し、Support SQLite API と SQLite ドライバ API の両方が想定どおりに動作します。これにより、増分移行が可能になり、1 回の変更ですべての Support SQLite の使用箇所を SQLite ドライバに変換する必要がなくなります。
Room SQLite Wrapper を使用する(省略可)
androidx.room:room-sqlite-wrapper アーティファクトは、移行中に SQLiteDriver と SupportSQLiteDatabase の間をブリッジする API を提供します。
SQLiteDriver で構成された RoomDatabase から SupportSQLiteDatabase を取得するには、新しい拡張関数
RoomDatabase.getSupportWrapper() を使用します。この互換性ラッパーは、SQLiteDriver を採用しながら、SupportSQLiteDatabase の既存の使用箇所(多くの場合、RoomDatabase.openHelper.writableDatabase から取得)を維持するのに役立ちます。特に、BundledSQLiteDriver を使用する SupportSQLite API の使用箇所が多いコードベースの場合に役立ちます。
移行サブクラスを変換する
移行サブクラスは、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) {
// …
}
}
@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 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 は、Kotlin が複数のプラットフォーム向けに提供する機能豊富な非同期 kotlinx.coroutines ライブラリを利用しています。最適な機能を実現するため、既存のコードベースとの下位互換性を維持するために androidMain に実装された DAO を除き、KMP プロジェクトでコンパイルされた DAO には suspend 関数が適用されます。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 = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Android のみ
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
書き込みトランザクション
書き込みトランザクションを使用すると、複数のクエリがデータをアトミックに書き込み、読み取り元が一貫してデータにアクセスできるようになります。これを行うには、次の 3 種類のトランザクション タイプで useWriterConnection を使用します。
immediateTransaction:ログ先行書き込み(WAL)モード (デフォルト)では、このタイプのトランザクションは開始時にロックを取得しますが、 読み取り元は引き続き読み取ることができます。ほとんどの場合、これが推奨されます。deferredTransaction: トランザクションは、最初の書き込みステートメントまでロックを取得しません。このタイプのトランザクションは、トランザクション内で書き込みオペレーションが必要になるかどうかわからない場合に、最適化として使用します。たとえば、プレイリストの名前だけを指定してプレイリストから曲を削除するトランザクションを開始し、そのプレイリストが存在しない場合、書き込み(削除)オペレーションは必要ありません。exclusiveTransaction: このモードは、WAL モードのimmediateTransactionと同じように動作します。他のジャーナリング モードでは、トランザクションの実行中に他のデータベース接続がデータベースを読み取ることができなくなります。
読み取りトランザクション
読み取りトランザクションを使用すると、データベースから一貫して複数回読み取ることができます。たとえば、2 つ以上の別々のクエリがあり、JOIN 句を使用しない場合などです。読み取り元接続では、遅延トランザクションのみが許可されます。読み取り元接続で immediate トランザクションまたは exclusive トランザクションを開始しようとすると、例外がスローされます。これらは「書き込み」オペレーションと見なされるためです。
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Kotlin Multiplatform では使用できない
Android で使用できた API の一部は、Kotlin Multiplatform では使用できません。
クエリ コールバック
クエリ コールバックを構成するための次の API は共通で使用できないため、Android 以外のプラットフォームでは使用できません。
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
今後のバージョンの Room でクエリ コールバックのサポートを追加する予定です。
クエリ コールバック
RoomDatabase.Builder.setQueryCallback とコールバック インターフェース
RoomDatabase.QueryCallback を使用して RoomDatabase を構成する API は共通で使用できないため、Android 以外のプラットフォームでは使用できません。
データベースの自動クローズ
タイムアウト後に自動クローズを有効にする API RoomDatabase.Builder.setAutoCloseTimeout は Android でのみ使用でき、他のプラットフォームでは使用できません。
データベースを事前パッケージ化する
既存のデータベース(事前パッケージ化されたデータベース)を使用して RoomDatabase を作成する次の API は共通で使用できないため、Android 以外のプラットフォームでは使用できません。これらの API は次のとおりです。
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
今後のバージョンの Room で事前パッケージ化されたデータベースのサポートを追加する予定です。
マルチインスタンスの無効化
マルチインスタンスの無効化を有効にする API RoomDatabase.Builder.enableMultiInstanceInvalidation は Android でのみ使用でき、共通または他のプラットフォームでは使用できません。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- Room KMP に既存のアプリを移行する Codelab
- KMP のスタートガイドの Codelab
- Room を使用してローカル データベースにデータを保存する