そろそろ Kotlin Coroutine をちゃんと使ってみよう

# TL;DR

```kotlin
launch {
    val result = opTakesLongEnoughToHaveMyNeckLong()
    showOffResult(result)
}
```

そろそろ、 Kotlin でも非同期処理はこんな風に書きたい時代

---
# ならば、実際にそう書いてみよう!

---
# そもそもコルーチンとは

みたいな話をし始めると日が暮れてしまったり居眠りしてしまったり首が伸びてしまったりするので、ばっさり割愛。

(JVM レベルにどう落とし込まれているかなど、興味深い話題もありますが)

---
# 導入
今回は、実践的なものを作ってみるという趣旨から、 Android アプリをターゲットとしてみます。

ということで、まずは `app/build.gradle` でコルーチン関連の基礎的なライブラリへの依存関係を追加します。

```gradle
dependencies {
    // ...
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
    // ...
}
```

※バージョンは本稿執筆時点(2019/05/22)の最新

---
## なぜライブラリ?
なぜでしょうね?

言語としての Kotlin には非同期処理を表現するに足る最低限のキーワードのみ持たせて、実装はプラットフォームによって自由にすげ替えることのできるような構造を目指した、といったところでしょうか。

---
# 実践
非同期といえば?
## 通信!
ですよね。
ということで、 Java 系界隈で REST API を抽象化するライブラリとしては定番中の定番、 [Retrofit](http://www.yahoo.co.jp)を組み合わせてみます。

---
`app/build.gradle` に追加するのはこんな感じ。

```gradle
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}
```

ちなみに、 `converter-gson` は、これも定番の JSON パーサー `gson` を Retrofit から透過的に使えるようにするためのもの。コルーチンを使う使わないには関係しないので、別のパーサーの方が好きならばそちらを使えばいいです。

---
## REST API を Kotlin の `interface` に
Retrofit は、アノテーションをベースに REST API を記述するタイプのライブラリです。
例えば、某サービスのログイン API はこのような記述になります。

```kotlin
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Deferred
import retrofit2.http.POST
import retrofit2.http.Query

interface Login {
    @POST("/api/member/logins")
    fun login(
        @Query("mail") mail: String
        , @Query("password") password: String
        , @Query("keep_me") keepMe: Boolean
    ): Deferred<LoginResult>
}

data class LoginResult(
    @SerializedName("r") val resultCode: String
    //, @SerializedName("d") val resultData: LoginData?
)
```

本当は `login(...)` そのものを `suspend fun` にしてしまいたいところですが、 Retrofit は直接対応していないので、 `CoroutineCallAdapterFactory` の助けを借りつつ、一旦 `kotlinx.coroutines.Deferred<T>` を返すものとして宣言してしまいます。

---
## `Deferred<T>` を返す Retrofit のメソッドを `suspend fun` に仕立て上げる
```kotlin
object ApiService {
    private val okHttpClient = OkHttpClient.Builder()
        // ...
        .build()

    @PublishedApi internal val retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
        .client(okHttpClient)
        .build()

    suspend inline fun <reified T, reified U> request(block: T.() -> Deferred<U>): U =
        block(retrofit.create(T::class.java)).await()
}
```

これで、 `val res = ApiService.request<Login, LoginResult> { login("aaa", "pass", keepMe = true) }` のような記法が可能になります。

---
## `suspend fun` とそうじゃないコードとのつなぎ目
### `CoroutineScope` と `CoroutineContext` と...
:confused: 

通常の `fun` から `suspend fun` を直接呼ぶことはできないので、一番最初に非同期処理を始めるところはどう書けばいいんだ? となってしまいます。

`CoroutineScope` インタフェースを実装したオブジェクトなら `launch` などの `suspend` なブロックを取れるメソッドが使えるようになるので、なんとかして手頃なオブジェクトに `CoroutineScope` になってもらう作戦になります。

---
### 単純な例: `Activity` が `CoroutineScope` になれればいいじゃない

```kotlin
class MainActivity: Activity(), CoroutineScope {
    // onDestroy() で 未完了のコルーチン呼び出しをまとめて cancel しまくるための CoroutineContext
    private val supervisor = SupervisorJob()

    // CoroutineScope になるためにはこの抽象プロパティーを実装しないといけない
    override val coroutineContext: CoroutineContext
        // メインスレッドを表すグローバルな CoroutineContext と、 supervisor スコープを合成した CoroutineContext が、
        // CoroutineScope としてのこの Activity の CoroutineContext となるようにする。
        get() = Dispatchers.Main + supervisor

    override fun onDestroy() {
        super.onDestroy()
        // 未完了のコルーチン呼び出しをまとめて始末
        supervisor.cancelChildren()
    }

    // ...
}
```

これで、 `Activity` の中なら `launch` その他が使えるようになりました。

---
### いよいよ、 onClickListener から接続!

```kotlin
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        loginButton.setOnClickListener {
            performLogin()
        }
    }

    private fun performLogin() {
        val mail = mailForm.text.toString()
        val password = passwordForm.text.toString()

        launch {
            // ここからコルーチンの世界!
            val loginResult = ApiService.request<Login, LoginResult> {
                login(mail, password, keepMe = false)
            }
            if (loginResult.resultCode != "00101") {
                // 失敗
                return@launch
            }
            // ...
        }
    }
```

---
# その他
## 例外? 例外!
コルーチンの中で例外が起きたらどうする? 問題。

端的には、 `suspend fun` 本体から別の `suspend fun` を呼んでいて、その中で例外が起きうる時にその例外を捕まえたければ、通常の同期的な書き方と同じ要領で普通に `try {} catch {}` でくるめば意図通りの意味になります。

```kotlin
    val loginResult = try {
        ApiService.request<Login, LoginResult> {
            login(mail, password, keepMe = false)
        }
    } catch (e: IOException) {
        Log.d(TAG, "login failed", e)
        return@launch
    }
    // ...
```

---
## いくつかのリクエストを並行させたい
`CoroutineScope#awaitAll()` など。キャンセル可能にする範囲を細かく管理したい場合はまた違った戦略が必要ですが、単純なケースならこれで十分でしょう。

```kotlin
    awaitAll(
        async { foo() }
        , async { bar() }
        , async { baz() }
    )
```

---
## Rx とどちらがいいか?
そもそも基本思想が違うので、適材適所といったところ。

* 複雑なタイミング制御などが必要なら、データフローを宣言的に記述できる Rx が有利。
* シンプルな一連の非同期処理だけなら、逐次処理に近い記法で記述できるコルーチンが有利。