RoomにCoroutineサポートが入ったので触ってみた

Diverse Advent Calendar 2018 12日目です。

昨日は@aono099 による [みんなでやる「他社アプリを見る会」](https://note.mu/yuaono/n/ndf02998ec9f7)でした。

今日はこの記事のネタ探しに[Android Architecture Componentsのリリースノート(英語版)](https://developer.android.com/jetpack/docs/release-notes)をふと見てみたら、Roomの2.1.0-alpha03からKotlin Coroutinesのサポートが入っていたので触ってみたお話です。

12月4日にリリースされたばかりのアルファ版なので実戦投入は控えたほうが安全そうですが、参考になると思うので書きます。

<img width="885" alt="スクリーンショット 2018-12-10 12.31.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/59465/bfd863e9-6415-a748-2be6-9c7068b5cb1a.png">

# なにができるようになったのか

DAOでsuspend functionが使えるようになりました！

```kotlin
@Dao
interface HogeDao {
    @Insert(onConflict = OnConflictStrategy.ABORT)
    suspend fun insert(fooEntity: FooEntity)

    @Update
    suspend fun update(fooEntity: FooEntity)

    @Query("SELECT * FROM foo_table")
    suspend fun findAll(): List<FooEntity>
}
```

CoroutineScopeの中でなら簡単にノンブロッキング処理が書けるようになりました。

```kotlin
val job = Job()
val dao = db.hogeDao()
CoroutineScope(Dispachers.Main + job).launch {
    dao.insert(FooEntity())
}
```

# 導入

```gradle
// kotlin coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1"

// Room
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-coroutines:$room_version"
```

`androidx.room:room-coroutines`を追加するとコルーチンのサポートが導入されます。
また、Kotlin Coroutinesを使うので`kotlinx-coroutines-core`と`kotlinx-coroutines-android`の2つのライブラリ追加をお忘れなく。 

# 使い方

## Room

DataBaseとEntityはこれまでと同様で、書き方が変わるのはDAOです。といっても書き方自体は大きな変更はなく、`suspend`キーワードを付けてあげるだけです。

```kotlin
@Dao interface UserDao {
    @insert
    suspend fun insert(entity: UserEntity)
    @Query("SELECT * FROM users")
    suspend fun findAll(): List<UserEntity>
}
```

## Activity

Roomの準備が終わったらそれを使ってSQLiteへアクセスしてみます。すべて書くと長くなるので、かなり省略しています。
(Activityに直書きせずViewModelやRepositoryなどを作ってDagger2等を用いてDIしたほうがテストが書きやすくなります)

```kotlin
class MainActivity : AppCompatActivity() {
   private val job = Job()
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val entity: UserEntity = UserEntity("taro")
        val dao = database.userDao()
        CoroutineScope(Dispachers.Main + job).launch {
            dao.insert(entity)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
}
```
Coroutine Scopeを作成しその中でDAOを呼び出す感じです。デフォルトだとRoomが用意したExecutorが使われるのでCoroutine Contextをスイッチする必要はないようです。

```kotlin
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class CoroutinesRoom {

    companion object {
        @JvmStatic
        suspend fun <R> execute(db: RoomDatabase, callable: Callable<R>): R {
            return withContext(db.queryExecutor.asCoroutineDispatcher()) {
                callable.call()
            }
        }
    }
}
```

簡単ですね！ ~~コルーチンは難しいけど~~

# まとめ

Kotlin 1.3になりコルーチンが正式リリースされ、Roomにも(アルファ版ですが)いつの間にかサポートが入ったので触ってみたお話でした。
Roomはどうやってsuspend functionを実現しているのか？といった内部の話はsys1yagiさんが[ブログ](http://sys1yagi.hatenablog.com/entry/2018/12/09/163450)で書いているのでそちらを参照されるとより深く理解できるのではないかと思います。

---
明日は@abuiがなにか書いてくれるようです。お楽しみに！