Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、データベースへのより安定したアクセスを可能にし、SQLite を最大限に活用できるようにします。このページでは、Kotlin マルチプラットフォーム(KMP)プロジェクトで Room を使用する方法について説明します。Room の使用方法について詳しくは、Room を使用してローカル データベースにデータを保存するまたは公式サンプルをご覧ください。
依存関係を設定する
KMP プロジェクトで Room を設定するには、KMP モジュールの build.gradle.kts
ファイルにアーティファクトの依存関係を追加します。
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)
}
ルート dependencies
ブロックに KSP 依存関係を追加します。アプリで使用するすべてのターゲットを追加する必要があります。詳しくは、Kotlin マルチプラットフォームでの 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 モジュールの共通ソースセット内に、@Database
アノテーションを付けたデータベース クラスと DAO、エンティティを作成する必要があります。これらのクラスを共通ソースに配置すると、すべてのターゲット プラットフォームで共有できるようになります。
// 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
のメンバーAndroidSQLiteDriver
人iosMain
のメンバー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.room.RoomDatabase { <init>(); }
Kotlin マルチプラットフォームに移行する
Room は元々 Android ライブラリとして開発されましたが、API の互換性を重視して KMP に移行されました。Room の KMP バージョンは、プラットフォーム間や Android 固有のバージョンと若干異なります。これらの違いは、次のとおりです。
Support SQLite から SQLite Driver に移行する
androidx.sqlite.db
での SupportSQLiteDatabase
やその他の API の使用は、SQLite Driver API でリファクタリングする必要があります。androidx.sqlite.db
の API は Android 専用であるためです(KMP パッケージとは異なるパッケージであることに注意してください)。
下位互換性を確保するため、RoomDatabase
が SupportSQLiteOpenHelper.Factory
で構成されている(たとえば SQLiteDriver
が設定されていない)限り、Room は「互換モード」で動作し、Support SQLite API と SQLite Driver API の両方が想定どおりに動作します。これにより、増分移行が可能になり、すべての Support SQLite の使用を 1 回の変更で SQLite Driver に変換する必要がなくなります。
移行サブクラスを変換する
移行サブクラスは、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) {
// …
}
}
@RawQuery
DAO 関数を変換する
Android 以外のプラットフォーム用にコンパイルされる @RawQuery
でアノテーションが付けられた関数は、SupportSQLiteQuery
ではなく RoomRawQuery
型のパラメータを宣言する必要があります。
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 は、Kotlin が複数のプラットフォーム向けに提供する機能豊富な非同期 kotlinx.coroutines
ライブラリのメリットを享受しています。機能を最適化するため、KMP プロジェクトでコンパイルされた DAO には suspend
関数が適用されます。ただし、既存のコードベースとの下位互換性を維持するために androidMain
で実装された DAO は除きます。KMP で Room を使用する場合、Android 以外のプラットフォーム用にコンパイルされたすべての DAO 関数は 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 関数が suspend 関数である必要はありません。LiveData
や RxJava の Flowable
などのリアクティブ型を返す DAO 関数は、suspend 関数に変換しないでください。ただし、LiveData
などの一部のタイプは KMP と互換性がありません。リアクティブな戻り値の型を持つ DAO 関数は、コルーチン フローに移行する必要があります。
Kotlin マルチプラットフォーム
リアクティブ タイプ 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 マルチプラットフォーム
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
句を使用していない場合などです。リーダー接続では、遅延トランザクションのみが許可されます。リーダー接続で即時トランザクションまたは排他トランザクションを開始しようとすると、例外がスローされます。これらは「書き込み」オペレーションとみなされるためです。
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Kotlin マルチプラットフォームでは利用不可
Android で利用可能だった API の一部は、Kotlin Multiplatform では利用できません。
クエリ コールバック
クエリ コールバックを構成するための次の API は共通ではなく、Android 以外のプラットフォームでは使用できません。
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
Room の今後のバージョンで、クエリ コールバックのサポートを追加する予定です。
クエリ コールバック RoomDatabase.Builder.setQueryCallback
とコールバック インターフェース RoomDatabase.QueryCallback
を使用して RoomDatabase
を構成する API は、共通では利用できないため、Android 以外のプラットフォームでは利用できません。
Auto Closing Database
タイムアウト後に自動的に閉じることを有効にする API RoomDatabase.Builder.setAutoCloseTimeout
は、Android でのみ使用でき、他のプラットフォームでは使用できません。
事前パッケージ化されたデータベース
既存のデータベース(あらかじめパッケージ化されたデータベースなど)を使用して RoomDatabase
を作成する次の API は common では利用できないため、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 を使用してローカル データベースにデータを保存する