AndroidでRoom+RxJavaを実装する

本記事ではAACのRoomPersistenceLibraryとRxJavaを組み合わせた、基本的な実装のお話をしていこうと思います。
記事内のコードはKotlinで記述していますが、Coroutineなどは使っていないため、基本的にJavaに置き換えることも可能です。

# 対象読者
- Room初学者
- Room + RxJavaに興味がある方

# セットアップ
最初にbuild.gradle(app)に以下を記述します。

```gradle
def room_version='2.1.0-alpha06'
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-rxjava2:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
```

- `androidx.room:room-rxjava2`を記述することによって、DAOの戻り値でSingleやMaybeなどを指定することができます。
- `room_version`に関しては`2.1.0`以上を使ってください。理由は後ほど詳しく記述しますが、2.1.0からInsertやdeleteの戻り値にCompletableを指定できるようになったからです。
- AndroidXに対応していないと、`2.0.0`以上のバージョンを使うことができないため、`1.1.1`を使ってください。詳しくは [こちら](https://developer.android.com/jetpack/androidx/releases/room#pre-androidx_dependencies) のサイトが参考になると思います。

# Entity
次にEntityを記述します。

```kotlin
@Entity(tableName = "user")
data class UserEntity constructor(
    @PrimaryKey(autoGenerate = true) //default false
    val id: Long = 0,

    @ColumnInfo(name = "user_name")
    val userName: String,

    val createAt: String
)
```
- `@Entity`これをつけることによって、Roomで利用できるEntityになります。`tableName`はオプションで、クラス名と実際に生成されるTable名を変えたいときにつけます。
- `@PrimaryKey`は、１つのEntityに最低１つはつける必要があります。複数のPrimaryKeyを持つ場合は、以下のように`@Entity`アノテーション内に定義できます。

```kotlin
@Entity(primaryKeys = ["id", "user_name"])
```
- `autoGenerate = true`になっている場合、idに`0`を渡すと、自動的にincrementしてくれます。idを指定する必要がない場合は、`0`を指定してください。

- `@Ignore`を定義することによって、そのカラムを無視することができます。しかしdata classで`@Ignore`を定義すると`Entities and Pojos must have a usable public constructor.`というエラーが吐かれます。これは全てのカラムにデフォルト値を指定することによって解消できます。

```kotlin
@ColumnInfo(name = "user_name")
var userName: String = "",

@Ignore
var createAt: String = ""
```

# DAO
次にData Access Object(DAO)を記述します。

```kotlin
@Dao
interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertiOrUpdateUser(entity: UserEntity): Completable

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertiOrUpdateUsers(entities: List<UserEntity>): Completable

    @Query("SELECT * FROM user WHERE id = :id")
    fun findById(id: Long): Single<UserEntity>

    @Delete
    fun delete(entity: UserEntity): Completable
}
```
- `@Insert`は、UserEntity単体、複数（List）のどちらでもInserすることができます。また、`onConflict`は、任意の値を指定することによって、PrimaryKeyが同じものがInsertされた場合の挙動を指定することができます。今回は上書きするように`OnConflictStrategy.REPLACE`を指定しました。その他の指定については [こちら](https://sqlite.org/lang_conflict.html) のサイトが参考になると思います。

- `@Insert`の戻り値はCompletableを指定できます。
- `@Delete`では引数にUserEntityを渡すことによって、渡したEntityを削除することができます。この戻り値もCompletableを指定することができます。

## Query
`@Query`の戻り値にはMaybe, Single, Flowable/Observableを指定することができます。各々挙動が違うので、要件に合ったものを選ぶといいと思います。

### Maybe
```kotlin
@Query("SELECT * FROM user WHERE id = :id")
fun findById(id: Long): Maybe<UserEntity>
```
- データベース内に該当するUserEntityが`ない`場合、何も返さずに`onComplete`が呼ばれます。
- データベース内に該当するUserEntityが`ある`場合、`onSuccess`が呼ばれ、終了します(onCompleteは呼ばれません)。
- Maybeが終了した後にUserEntityが更新されても何もしません。

### Single
```kotlin
@Query("SELECT * FROM user WHERE id = :id")
fun findById(id: Long): Single<UserEntity>
```
- データベース内に該当するUserEntityが`ない`場合、`onError(EmptyResultSetException)`が呼ばれます。
    - データベース内に該当するUserEntityが`ない`かつ、List型を返す場合は`onError(EmptyResultSetException)`が呼ばれず、空のListが返されます。
- データベース内に該当するUserEntityが`ある`場合、`onSuccess`が呼ばれ、終了します。
- Singleが終了した後にUserEntityが更新されても何もしません。

### Flowable/Observable
```kotlin
@Query("SELECT * FROM user WHERE id = :id")
fun findById(id: Long): Flowable<UserEntity>
```
- データベース内に該当するUserEntityが`ない`場合、何も返さず、何も呼ばれません。
- データベース内に該当するUserEntityが`ある`場合、`onNext`が呼ばれます。
- UserEntityが更新されるたびに、自動で`onNext`が呼ばれます。

# Database
次にDatabaseを記述します。

```kotlin
@Database(entities = [UserEntity::class], version = SampleDatabase.DATABASE_VERSION)
abstract class SampleDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

    companion object {
        const val DATABASE_VERSION = 1
        private const val DATABASE_NAME = "sample.db"

        private var instance: SampleDatabase? = null

        fun init(context: Context) {
            Room.databaseBuilder(context, SampleDatabase::class.java, DATABASE_NAME)
                .build().also { instance = it }
        }

        fun getInstance() = instance
    }
}
```
- RoomDatabaseを継承したabstractクラスを作成します。
- `@Database`内の`entities`にアプリ内で使うEntityを宣言します。Entityが増えるたびに、ここに追加していく形になります。
- `@Database`内の`version`にデータベースのversionを定義します。最小値は1です。
- `abstract fun userDao(): UserDao`の部分はDAOの宣言になります。DAOが増えるたびに、ここに追加していくことになります。
- init()やgetInstance()を生やしておき、外部から呼び出せるようにしておきます。


## Databaseの初期化
最後に上で定義したDatabaseをApplicationクラスで初期化します。

```kotlin
class SampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        SampleDatabase.init(this)
    }
}
```
簡単ですね。これでSampleDatabaseをどこからでも呼び出せるようになりました。

# 使ってみる
それでは準備が整ったので、実際に使ってみようと思います。
今回は
1. 複数のUserEntityをInsert
2. idからUserEntityを取得
3. ２で取得したUserEntityを削除
この３つの処理を書いていこうと思います。

```kotlin
val users: List<UserEntity> = //任意の値を入れてください

SampleDatabase.getInstance()?.let { sampleDatabase ->
    val userDao = sampleDatabase.userDao()
     
    userDao
        .insertiOrUpdateUsers(users)
        .andThen(userDao.findById(1)) //Single<UserEntity>を返しています
        .flatMapCompletable { user -> userDao.delete(user) }
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({}, { e -> e.printStackTrace() })
}
```
たったこれだけです！ すごくシンプルに書けます。

- `subscribeOn()`ここだけ説明しておきます。Room実行は基本的にBackgroundThreadを指定しないといけません。MainThreadで実行するとExceptionが吐かれます。もしMainThreadでの実行を許可したい場合は、RoomのInstance生成時に以下のような定義が必要になります。

```kotlin
Room.databaseBuilder(context, SampleDatabase::class.java, DATABASE_NAME)
    .allowMainThreadQueries() //MainThreadでの実行を許可します
    .build().also { instance = it }
```

# 最後に
RoomはThread制限があったり、Queryが間違っていたらBuild時に教えてくれたり、とても親切なライブラリです。
皆さんも是非使ってみてください。

## 参考文献
- https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757
- https://android.benigumo.com/20171201/room-rxjava/
- https://qiita.com/yukiyamadajp/items/73bffb6a3697cb62f9e1
- https://github.com/googlesamples/android-architecture-components/tree/master/BasicRxJavaSampleKotlin