［Android］Room を使ったサンプルと解説

# はじめに

とりあえずこれに従えばできるみたいなサンプルを作ろうと思います。
サンプルは次の手順で作成していきたいと思います。

0. Rooｍ をセットアップする
1. Entity(User) を定義する
2. Dao(UserDao) を定義する
3. RoomDatabase(AppDatabase) クラスを定義する

| 役割 | クラス名 |　役割 |
| ----- | ------- | ------- |
| Entity | User | データベースのテーブルを表すクラス |
| Data Access Objects | UserDao | データベースにアクセスするメソッドを定義するクラス |
| RoomDatabase | AppDatabase | Daoを生成するためのRoomDatabaseを継承した抽象クラス |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/9a63c3bf-1db3-d8e2-1685-b15a1ac9d36f.png)


# 0. Roomをセットアップする

次の依存関係を記述し Room をセットアップする。
KTX や Coroutines を利用する場合には `room-ktx`を入れます。
Kotlin を利用するならば `annotationProcessor` ではなく`kapt` を利用するようにします。

```groovy:build.gradle(app)
apply plugin: 'kotlin-kapt'

dependencies {
      def room_version = "2.2.2"
      implementation "androidx.room:room-runtime:$room_version"
      implementation "androidx.room:room-ktx:$room_version"
      kapt "androidx.room:room-compiler:$room_version"
}
```

# 1. Entity ( User ) を定義する
データベースにテーブルを表す Entity を定義します。
Entity は `@Entity` をクラスに付けるだけで定義できます。
デフォルトだとテーブル名称としてクラス名称が登録されるようになっています。
必要であれば `tableName` にてテーブル名称を変更してください。

```Kotlin
@Entity(tableName = "users")
data class User(val id: Int, val firstName: String?, val lastName: String?, val age: Int)
```

Entity には必ず 1つの主キーを指定する必要があります。
主キーは `@PrimaryKey` アノテーションをクラスのプロパティにつけるだけで指定します。
また `@PrimaryKey` では `autoGenerate` にて自動生成するか設定できるようになっています。
なので必要に応じて `autoGenerate = true` を付けて、主キーを自動生成するようにして下さい。

```Kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int,
    val firstName: String?,
    val lastName: String?,
    val age: Int
)
```

デフォルトだとテーブルに登録されるカラム名称はプロパティ名称が登録されるようになっています。
登録されるカラム名称は`@ColumnInfo` で変更できるようになっています。
なので必要に応じて　`@ColumnInfo(name ="xxxxx")` を付けて、カラム名称を変更するようにしてください。

```Kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    val age: String?
)
```

デフォルトだと Entity に定義される全てのプロパティをテーブルのカラムとして登録します。
あるプロパティをテーブルに含めたくない場合、`@Ignore`を付けることでテーブルに含めないようにできます。

```Kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @Ignore val age: String?
) {
    // @Ignore を付ける場合は、テーブルに含めるプロパティで初期化するコンストラクタを作成する必要があります。
    constructor(id: Int, firstName: String?, lastName: String) : this(id, firstName, lastName, null)
}
```

# 2. Dao ( UserDao ) を定義する

データベースにアクセスするクラスである Dao を定義していきます。
Dao は `@Dao` をインタフェースに付けるだけで定義できます。
このインタフェースに挿入・削除・更新・取得を行うメソッドを実装していきます。

```Kotlin
@Dao
interface UserDao {}
```

挿入は `@Insert` 、削除は`@Delete`、更新は`@Update`を付けて定義します。
このようにアノテーションを定義すれば、あとは Room が勝手に処理を実装してくれます。

```Kotlin
@Dao
interface UserDao {
    @Insert
    fun insert(user : User)

    @Update
    fun update(user : User)

    @Delete
    fun delete(user : User)
}
```

`@Query` を付けば自身で記述したクエリを実行できるようにします。
`@Query` には引数に指定したパラメータをクエリに含めることもできます。

```Kotlin
@Dao
interface UserDao {
    @Insert
    fun insert(user : User)

    @Update
    fun update(user : User)

    @Delete
    fun delete(user : User)

    @Query("delete from users")
    fun deleteAll()

    @Query("select * from users")
    fun getAll(): List<User>

    @Query("select * from users where id = :id")
    fun getUser(id: Int): User
}
```

# 3. RoomDatabase ( AppDatabase ) を実装する

RoomDatabase を拡張した抽象クラスである AppDatabase を生成します。

```Kotlin
abstract class AppDatabase : RoomDatabase() {}
```

`@Database` を付け、 `entities` に 1. で定義した Entity を指定します。

```Kotlin
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {}
```

そして 2.で定義した Dao を取得するためのメソッドを用意すれば実装完了です。

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

# おわりに

ここまで定義したクラスを利用して処理を記述する次の感じになります。
Room は Main スレッドで実行すると怒られるので、
Coroutine などを利用して Main スレッドで実行しないようにしましょう。

```Kotlin
    val database =
        Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name")
            .build()
    val userDao = database.userDao()
    val newUser = User(0, Date().time.toString(), Date().time.toString(), null)
    userDao.insert(newUser)
    Log.v("TAG", "after insert ${userDao.getAll().toString()}")
```

```
V/TAG: after insert [User(id=138, firstName=1579438392369, lastName=1579438392369, age=null)]
V/TAG: after deleteAll []
```

# 参考資料
- [Room | Android Developers](https://developer.android.com/jetpack/androidx/releases/room#declaring_dependencies)