AndroidでKotlin Coroutinesの使い所

kotlin-1.0

まえがき

Coroutinesは、Coroutines are experimental in Kotlin 1.1で、実験的な機能ですのでプロダクトにはまだ早いですが、とても便利なので今のうちから備えておきたいところです。

Coroutines - Kotlin Programming Language

kotlinx.coroutines/coroutines-guide.mdを写経しながら勉強しています。

サンプルアプリを作りながら、ある程度パターンが見えてきました。

コルーチン

Coroutines(コルーチン)は中断・再開可能な関数です。

こちらに丁寧な説明があります。導入の仕方もあります。

Kotlin+Androidでasync/await - Qiita

Androidで使用する場合の良さそうなパターンがありましたので、ご紹介します。

MVPでもMVVMでも、使用できるパターンです。

概要

MVPでもMVVMでも大まかにいってこんな処理をすると思います。

UIから(タップなど)イベント -> 重たい処理(Webのapiを叩くなど) -> 結果をUIに反映

コルーチンがない現状は、RxJavaなどを用いて処理していると思います。

RxJavaの例

fun click(){
     view.showLoading()
     repository.api()
                    .subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe({
                        view.hideLoading()
                        view.showSuccess(it)
                    }, {
                        view.showError(it)
                     })
}

パターン

重たい処理(Webのapiを叩くなど)の部分は処理している間は中断して他の処理をしてもらうようにsuspendをつけます。

class Repository{
    suspend fun api() : Response{
        //重たい処理
        return Response()
    }
}

UIから(タップなど)イベント結果をUIに反映の部分はUIスレッドで行えるようにします。

launch(UI)をつかってUIスレッドで処理するように指定します。

fun click() = launch(UI) {
}

そして、RepositoryのapiをUIスレッド以外で処理するようにCommonPoolを使用します。CommonPoolは新しいスレッドだと思ってください。apiはResponseを返すのでlaunchではなく asyncをつかって処理結果を受け取れるします。await()を使って、処理が終わるまで待ちます。その間は、中断され、処理が終わった時に再開されます。(非同期っぽく処理できる)

fun click() = launch(UI){
    val response = async(CommonPool){
        repository.api()
    }.await()
}

その他付属品をつけてあげて

fun click() = launch(UI) {
    view.showLoading()
    val repository = Repository()
    try {
        val response = async(CommonPool) {
            repository.api()
        }.await()
        view.hideLoading()
        view.showSuccess(response)
    }catch (e: Throwable){
        view.view.showError(e)
    }
}

これで完成!

っと言いたいところだけど、このままだと、activityでonStopでJobをキャンセルしたいときにCommonPoolの処理がキャンセルされません。キャンセルできるように修正する必要があります。

こちらに丁寧な説明があります。AndroidでKotlin Coroutineを非同期で使うときの注意 – STAR_ZERO – Medium

fun click() = launch(UI) {
    view.showLoading()
    val repository = Repository()
    try {
        val response = async(context + CommonPool) {
            repository.api()
        }.await()
        view.hideLoading()
        view.showSuccess(response)
    }catch (e: Throwable){
        view.view.showError(e)
    }
}

まとめ

ソースコードが同期的な書き方でとても読みやすいです。非同期で依存関係がある場合コールバック地獄になりがちでしたが、とても簡潔に記述できるようになります。次回コルーチンでのUnitTestについて書きたいと思います。

参考