Room を使用してローカル データベースにデータを保存する   Android Jetpack の一部。

Kotlin Multiplatform で試す
Kotlin Multiplatform を使用すると、データベース レイヤを他のプラットフォームと共有できます。KMP で Room データベースを設定して使用する方法について説明します

比較的大量の構造化データを処理するアプリは、そのデータをローカルに永続化することで大きなメリットを得ることができます。最も一般的なユースケースは、デバイスがネットワークにアクセスできない場合でも、ユーザーがオフラインの間にコンテンツをブラウジングできるように、関連するデータをキャッシュに保存することです。

Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、データベースへのスムーズなアクセスを可能にし、SQLite を最大限に活用できるようにします。特に、Room には次のようなメリットがあります。

  • SQL クエリのコンパイル時検証。
  • 繰り返しが多く間違いを犯しやすいボイラープレート コードを最小限に抑える便利なアノテーション。
  • 効率的なデータベース移行パス。

SQLite API を直接使用するのではなく、Room を使用することをおすすめします。

設定

アプリで Room を使用するには、モジュールの build.gradle.kts ファイルに次の依存関係を追加します。Room 3.0 では、アノテーション処理に KSP が必要です。

Kotlin

dependencies {
    val room_version = "3.0.0"

    implementation("androidx.room3:room3-runtime:$room_version")
    ksp("androidx.room3:room3-compiler:$room_version")
}

Groovy

dependencies {
    def room_version = "3.0.0"

    implementation "androidx.room3:room3-runtime:$room_version"

    ksp "androidx.room3:room3-compiler:$room_version"
}

主要コンポーネント

Room は、次の 3 つの主要コンポーネントで構成されます。

データベース クラスは、そのデータベースに関連付けられている DAO のインスタンスをアプリに提供します。アプリはこの DAO を使用して、関連するデータ エンティティ オブジェクトのインスタンスとしてデータベースからデータを取得できます。また、定義されたデータ エンティティを使用して、対応するテーブルの行を更新したり、挿入用の新しい行を作成したりできます。図 1 に、Room のさまざまなコンポーネントの関係を示します。

図 1. Room ライブラリのアーキテクチャの図。

実装例

このセクションでは、1 つのデータ エンティティと 1 つの DAO で Room データベースを実装する例を示します。

データ エンティティ

次のコードは、User データ エンティティを定義しています。User の各インスタンスは、アプリのデータベースにある user テーブルの行を表します。

@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String,
    @ColumnInfo(name = "last_name") val lastName: String
)

Room のデータ エンティティの詳細については、Room エンティティを使用してデータを定義するをご覧ください。

データ アクセス オブジェクト(DAO)

次のコードは、UserDao という DAO を定義しています。UserDao は、user テーブルのデータを操作するためにアプリの他の部分が使用する関数を提供します。

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    suspend fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    suspend fun loadAllByIds(userIds: IntArray): List<User>

    @Query(
        """
        SELECT * FROM user
        WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1
        """
    )
    suspend fun findByName(first: String, last: String): User

    @Insert
    suspend fun insertAll(vararg users: User)

    @Delete
    suspend fun delete(user: User)
}

DAO の詳細については、Room DAO を使用してデータにアクセスするをご覧ください。

データベース

次のコードは、データベースを保持するために AppDatabase クラスを定義しています。AppDatabase はデータベース構成を定義し、永続データに対するアプリのメイン アクセス ポイントとして機能します。データベース クラスは次の条件を満たす必要があります。

  • クラスには、データベースに関連付けられたすべてのデータ エンティティをリストする entities 配列を含む @Database アノテーションを付ける必要があります。
  • クラスは、RoomDatabase を拡張する抽象クラスである必要があります。
  • データベースに関連付けられた DAO クラスごとに、引数を受け取らずに DAO クラスのインスタンスを返す抽象関数を定義する必要があります。

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

注: 単一のプロセスで動作するアプリの場合は、AppDatabase オブジェクトをインスタンス化する際にシングルトン デザイン パターンに従う必要があります。各 RoomDatabase インスタンスは非常に高コストであり、単一のプロセス内で複数のインスタンスにアクセスする必要はほとんどありません。

複数のプロセスで動作するアプリの場合は、データベース ビルダーの呼び出しに enableMultiInstanceInvalidation() を組み込みます。各プロセスに AppDatabase のインスタンスがある場合、あるプロセスで共有データベース ファイルを無効化すると、他のプロセス内の AppDatabase のインスタンスにも自動的に反映されます。

使用方法

データ エンティティ、DAO、データベース オブジェクトを定義したら、次のコードを使用してデータベースのインスタンスを作成できます。

val db =
    Room.databaseBuilder<AppDatabase>(applicationContext, "database-name")
        .setDriver(AndroidSQLiteDriver())
        .build()

その後、AppDatabase の抽象関数を使用して、DAO のインスタンスを取得できます。次に、DAO インスタンスの関数を使用して、データベースを操作できます。

val userDao = db.userDao()
val users: List<User> = userDao.getAll()

参考情報

Room の詳細については、以下の参考情報をご覧ください。

サンプル