KotlinでRoomを使う

今年は Room(a SQLite object mapping library) を Kotlinで扱うとどうなる、の話です。
基本的にはkaptを追加記述するだけなんですが、、一部kotlinだと楽になる部分もありますのでぜひRoomはkoltinでやっていきましょう。
本気で解説してもらってる記事には負けますが、実需がある程度簡単にRoomについても触れながらどう扱うかを書いていきます。

# Room?
Room Persistence Library　導入和訳

AndroidフレームワークはSQLiteをサポートしてる、こいつそのものはパワフルだけど、低レイヤーだから使いこなすには労力が必要だよね。

Roomを使うことで端末がインターネットに接続しているかどうか関係なくキャッシュの保存が可能（インターネットの環境に復帰した際に同期される）

SQLiteクエリはコンパイル時には検証されないため、必要なとき（スキーマが変更された等）開発者がクエリを変更する必要があり、バグの原因になりやすい。
しかもデータオブジェクト変更のためにお作法コードもめちゃくちゃ書かなきゃいけない。

Roomではこういう部分に抽象化レイヤーを用意することで対応したよ。

# 導入
gradleに

```
compile "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
kapt "android.arch.persistence.room:compiler:1.0.0"
```

kaptを追加したのみで、その他はjavaと同じです。

# Roomの構成
![room_architecture.png](https://qiita-image-store.s3.amazonaws.com/0/59803/fcc159fd-0313-2951-4acc-ffbc8b6e6c34.png)
[引用-AndroidDeveloper](https://developer.android.com/training/data-storage/room/index.html)
三つの要素がRoomを構成しています。

- entity
- DataAccessObject
- Room datebase

の３つです。
それぞれ用意して呼び出します。

Entityを用意して、DataAcessObjects(Dao)で操作を記述という形です。

# Entity

```
@Entity
data class Address constructor(
        @PrimaryKey(autoGenerate = true)
        val id: Long,
        val address: String,
        val name: String
)
```

`@Entity` Annotationを付けることでこのクラスがデータベースに保存されるEntityとなります。この辺はRealmとかと同じですね。
また、DataAccessObjectからこのEntityは見える必要があるのでgetter/setterを用意する必要があるわけですが、dataObjectにすることで省略することができるのが利点ですね。


`@PrimaryKey` をオブジェクトに一つはつける必要があります。

`autoGenerate` でてきとうなプライマリーキーを発行してくれます。

今回使わなかったのですが、

```
@ColumnInfo(name = "place_id") val id : Long
```

等やるとSQLを叩くときにはスネークケース、実際にコードを書くときはキャメルケースにすることができるわけです。

# DateAccessObjects
コード上では長いのでDaoと記述しました。

```
@Dao
interface AddressDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun createAddress(address: Address)

    @Query("SELECT * FROM Address")
    fun findAll(): List<Address>

    @Update
    fun updateAddress(address: Address)

    @Delete
    fun delete(address: Address)
}
```

Daoを書いてみました。

Annotationは全部で４つ。

- Insert
- Update
- Delete
- Query

です。そのままの意味ですが、`@Query` ではSQL文をそのまま記述することができます。今回は `findAll()` なのでそのようなSQL文を書いてあります。（この部分は流石にどうしようもなかったか）

Insertに `onConflict` に書いてありますが、コンフリした際に指定できるのが５つ。

- ABORT
- FAIL
- IGNORE
- REPLACE
- ROLLBACK

です。そのままです。今回はREPLACEしてもらえればいいのでそのようにしています。

# Datebase

```
@Database(entities = arrayOf(AddressDao::class), version = 1)
abstract class DataBase : RoomDatabase() {
    abstract fun addressDao(): AddressDao
}
```

arrayOfのなかにじゃんじゃんDaoを追加していくことでこのDatabaseを呼び出します。
更新があるたびにversionを上げていくことになります。また、その他のDaoが増えたときにはここに追加していくことになります。

# Database呼び出し
```
class SampleApplication : Application() {
    companion object {
        lateinit var database: DataBase
    }

    override fun onCreate() {
        super.onCreate()

        database = Room.databaseBuilder(this, objectOf<DataBase>(), "kotlin_room_sample.db").build()
    }
}

internal inline fun <reified T : Any> objectOf() = T::class.java
```

サンプルなのでApplictionに置いてありますが、ホントはDagger2等で注入してあげたほうがテストは書きやすいかと思います。

# データの追加、呼び出し、削除

さて、ApplicationにDatabaseを置いたので呼び出してみましょう。

## create
```
        val dao = SampleApplication.database.addressDao()
        async(UI) {
            bg {
                dao.createAddress(address)
            }
        }
```

とはいえとても簡単です。
先ほどシングルトン代わりに置いたApplicationをdatabaseから持ってきたdaoを呼び出します。
で、まぁ一応RoomはUIスレッドでも実行することができるんですが、普通に考えてバックグラウンドで処理するべきなのでバックグラウンドでcreateします
ちなみにUIスレッドで使いたい場合はdatabaseに対して
```
.allowMainThreadQueries()
```
することでイケます。

## query
```
    private fun loadAddress() {
        async(UI) {
            val address: Deferred<List<Address>> = bg { getAddress() }
            adapter.addItems(address.await())
            adapter.notifyDataSetChanged()
            resetEmptyView()
        }
    }

    private fun getAddress(): List<Address> {
        val dao = SampleApplication.database.addressDao()
        return dao.findAll()
    }
```

次にQueryメソッドを呼び出してみます。
ここではList用に保存されたすべてのデータを持ち出します。
kotlinだとasync,awaitが使えるので効果的に記述することができます。

Roomからの取得を待ってListにデータの表示を反映させる、という具合ですね。

## delete
```
    private fun deleteAddress(address: Address) {
        async(UI) {
            bg {
                val dao = SampleApplication.database.addressDao()
                dao.delete(address)
            }
           // UI操作
        }
    }
```

次にdeleteをしてみます。
こちらもbgでdeleteします。ただし、UI操作はawaitしません。UI操作を待ってしまうと、ユーザーにはよくわからない時間が発生してしまうためです。

以上です。

# 終わりに
以上となります。Roomを書く上で恩恵を受けられる部分は、Data object と async,await あとはスレッド管理がkotlinだと簡単に書けますね。