Mokelab Blog

Roomを使ってみる

今回は Room を使ってみます。より高度な機能は別の回で紹介するかもしれません。

Room とは

Room とは SQLite をいい感じに扱うためのライブラリです。Android では SQLiteDatabase クラスがデータベース操作を提供してくれますが、どうしてもボイラープレートコードが増えがちです。Room を使うとより簡潔にデータベース操作を行うアプリを記述することができます。

プロジェクトに Room を追加する

build.gradle に次の行を追加します。

implementation 'androidx.room:room-runtime:2.2.2'
kapt 'androidx.room:room-compiler:2.2.2'

また、kapt を使うので kotlin-kapt プラグインも追加します。kotlin-android-extensions の下に次の行を追加します(実験はしていませんが、プラグインの apply 順によってはうまく動作しないことがあります)。

apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' // この行を追加

エンティティを作る

まず、テーブルの 1 行分のデータを表すクラスを作ります。このようなクラスをエンティティと呼びます。 クラスの前に@Entity をつけておくと、Room はクラス名に対応したテーブルを自動で作成してくれます。

今回はユーザー情報を表すエンティティを作成してみます。

@Entity
data class User(
    @PrimaryKey(autoGenerate = true) val _id: Int = 0,
    val name: String
)

各プロパティがテーブルの列になります。@PrimaryKey をつけるとその列が主キーになります。

DAO を作る

次に、テーブルにアクセスするためのオブジェクト(Data Access Object)定義を作ります。Room では次のようにインターフェースを定義します。

@Dao
interface UserDAO {
    @Query("select * from user")
    fun getAll(): LiveData<List<User>>

    @Insert
    fun create(user: User)

    @Update
    fun update(user: User)

    @Delete
    fun delete(user: User)
}

アノテーション名がテーブルに対する操作と対応しているので、特に説明は不要でしょう。

データベースを作る

SQLite ではデータベースをファイルで保存しています。1 つのデータベースには複数のテーブルが入っているので、どのテーブルが入っているかを記述してあげる必要があります。Room では次のような抽象クラスを定義します。

@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDAO(): UserDAO
}

entities でどのエンティティ(=テーブル)がデータベースに含まれるかを指定します。 version はデータベースのバージョンで、バージョンアップを自動検知しテーブル定義を更新してくれます。抽象メソッドとして先ほど定義した DAO を返すメソッドを定義します。

インスタンスを作る

次に UserDatabase インスタンスを作ります。インスタンスの生成は高コストで、アプリプロセスで 1 つあればよいので次のようにシングルトンにします。

@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDAO(): UserDAO

    companion object {
        private const val DB_NAME = "user.db"
        private lateinit var instance: UserDatabase

        fun getInstance(context: Context): UserDatabase {
            if (!::instance.isInitialized) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    UserDatabase::class.java,
                    DB_NAME
                ).build()
            }
            return instance
        }
    }
}

インスタンスの生成は Room.databaseBuilder()を使います。第 1 引数にはアプリコンテキストを指定します(間違ってアクティビティコンテキストを渡すとメモリリークするので要注意)。第 2 引数には先ほど定義したデータベースクラスを指定します。第 3 引数にはどのファイルにデータベースを作るか、ファイル名を指定します。

データを追加する

データベースを操作する準備ができました。早速データを追加しましょう。

と、その前に Kotlin コルーチンに関するライブラリを追加します。DAO で定義したメソッドは LiveData を返す get を除いて、UI スレッドで呼ぶとクラッシュするためです。

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

データの追加は次のように DAO で定義したメソッドを呼ぶだけです。

class MainFragment: Fragment() {
    ...
    private fun addItem() {
        val name = this.binding.usernameEdit.text.toString()

        viewLifecycleOwner.lifecycleScope.launch {
            val db = UserDatabase.getInstance(requireContext())
            // ここからはIOスレッドで実行してもらう
            withContext(Dispatchers.IO) {
                // テーブルに追加
                db.userDAO().create(User(name = name))
            }
            // 入力欄は消しておくか。。
            binding.usernameEdit.setText("")
        }
    }
}

データを取得する

データの取得は DAO で定義した get 系メソッドを呼ぶだけです。戻り値が LiveData なので、データベースに変更があった時点で自動で再取得が行われます。

class MainFragment: Fragment() {
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val db = UserDatabase.getInstance(requireContext())
        // LiveDataなので、監視して変更があったらUIに反映させるだけ
        db.userDAO().getAll().observe(this, Observer {
            it.forEach { user ->
                println("name=${user.name}")
            }
        })
    }
}

まとめ

Room の基本的な使い方を説明しました。簡単なメモ帳アプリであれば Room を使うとすぐ実装できるでしょう。Room には他にも機能がたくさんあります。本ブログでも少しずつ紹介していきます。

本サイトではサービス向上のため、Google Analyticsを導入しています。分析にはCookieを利用しています。