Room(Kotlin マルチプラットフォーム)

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 モジュールの共通ソースセット内に、DAO とエンティティとともに、@Database アノテーションを付けたデータベース クラスを作成する必要があります。これらのクラスを共通のソースに配置すると、すべてのターゲット プラットフォームで共有できるようになります。

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

@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
)

なお、実際の / 期待の宣言を使用すると、プラットフォーム固有の Room 実装を作成できます。たとえば、expect を使用して共通コードで定義されているプラットフォーム固有の DAO を追加し、プラットフォーム固有のソースセットで追加のクエリを使用して actual 定義を指定できます。

データベース ビルダーの作成

各プラットフォームで Room をインスタンス化するためのデータベース ビルダーを定義する必要があります。ファイル システム API の違いにより、これは API でプラットフォーム固有のソースセットに含める必要がある唯一の部分です。たとえば、Android では通常、データベースの場所は Context.getDatabasePath() API によって取得されますが、iOS の場合、データベースの場所は NSHomeDirectory を使用して取得されます。

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

データベース インスタンスを作成するには、データベース ファクトリとデータベース パスを指定します。データベース ファクトリは、KClass<T> 型のレシーバを使用して、生成された名前が instantiateImpl の拡張関数を呼び出すラムダ関数です。ここで、T@Database アノテーションを付けたクラスの型です。

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM(デスクトップ)

データベース インスタンスを作成するには、データベース パスのみを指定します。データベース ファクトリを指定する必要はありません。

// shared/src/commonMain/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,
    )
}

データベースのインスタンス化

プラットフォーム固有のコンストラクタのいずれかから 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 ライブラリの利点があります。機能を最適化するために、KMP プロジェクトでコンパイルされた DAO には suspend 関数が適用されます。ただし、既存のコードベースとの下位互換性を維持するため、Android 固有の DAO は除きます。

KMP との機能の違い

このセクションでは、KMP と Android プラットフォーム バージョンの Room の機能の違いについて説明します。

@RawQuery DAO 関数

Android 以外のプラットフォーム用にコンパイルされた @RawQuery アノテーション付きの関数は、エラーが発生します。Room の今後のバージョンで @RawQuery のサポートを追加する予定です。

クエリ コールバック

クエリ コールバックを設定するための次の 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 でのみ使用でき、一般的なプラットフォームや他のプラットフォームでは使用できません。