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 には他にも機能がたくさんあります。本ブログでも少しずつ紹介していきます。