Retrofit を Coroutines と共に使ってみる

2018-07-10 23:11:59 (modified at 2018-07-11 23:17:55) · 1230 words · 3 minute read Android

先日、Kotlin の Coroutines が Experimental でなくなったと聞いて、これは本格導入の機運だなと思ったのでひとまず Retrofit + RxKotlin を置き換えようと試みてみました。
そこで、ざっくりとした導入方法と、Jake 謹製の Coroutines 用 Retrofit アダプタの実装についてつらつらと書いてみたいと思います。

Retrofit で Coroutines

Retrofit に Deferred を 扱うためのアダプタを設定する

build.gradle (module) :

dependencies {
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:x.y.z'
}

RetrofitUtil.kt :

object RetrofitUtil {

    lateinit var retrofit: Retrofit

    fun init(context: Context) {
        retrofit = Retrofit.Builder()
                .addCallAdapterFactory(CoroutineCallAdapterFactory()) // これで Retrofit で Deferred が使えるようになる
                .baseUrl("...")
                .client(createOkHttpClient(context))
                .build()
    }

    private fun createOkHttpClient(context: Context): OkHttpClient =
            OkHttpClient.Builder().build()
}

Deferred で通信結果を受ける

Fuga.kt :

data class Fuga(
    val piyoList: List<Piyo>
) {
    data class Piyo(
        val id: Long,
        val name: String,
        val height: Float
    )
}

HogeService.kt :

interface HogeService {
    @GET("/fuga")
    fun getFuga(): Deferred<Fuga>
}

HogeClient.kt :

class HogeClient(private val retrofit: Retrofit = RetrofitUtil.retrofit) {

    suspend fun getPiyoList(): List<Piyo> =
            retrofit.create(HogeService::class.java) // Deferred<Fuga>
                    .await()  // Fuga
                    .piyo // Fuga.piyo (List<Piyo>)
}

これだけ?

これだけです。

これだけで Deferred で通信結果を受けられるようになる。すごい。
あなたも実装が気になってきたはずです。私は気になった。それでは見ていきましょう。

Kotlin Coroutine (Experimental) Adapter の実装を覗いてみる

めっちゃ薄い

このアダプタの実装は、150行足らずの1つのクラスにまとまっています。気持ちいいくらい薄いですね。

ほんのり解説

まず、Retrofit のアダプタなのでretrofit2.CallAdapter.Factory()を継承し、その中でさらにDeferred<T>が欲しい時用のCallAdapterDeferred<retrofit2.Response<T>>が欲しい時用のCallAdapterをそれぞれ定義しています。

要求された型がDeferredのジェネリクスでない場合はnullで早期リターンします。 また、要求された型がDeferredであるがそのデータ型が指定されていない場合には例外を吐きます。

そこを抜けると後はDeferred<T>が欲しいのかDeferred<Response<T>>が欲しいのかの判定を行い、それぞれのCallAdapterに渡します。

いずれのCallAdapterも実装はほとんど同じです。
まずCompletableDeferredのインスタンスを作り、このDeferredがキャンセルされた際に通信もキャンセルするためにCompletableDeferred#invokeOnCompletionCall#cancel()を記述します。

そして、Call#enqueueを実行し、コールバックとして、失敗時にはCompletableDeferred#completeExceptionally(Throwable)を、成功時にはCompletableDeferred#complete(T)を呼びます。

最後にこうして出来上がったDeferredreturnして完了です。

まとめ

非同期処理を完結に記述できる Coroutines 。
薄くて使い勝手の良いライブラリのおかげで今すぐにでも Retrofit と組み合わせて使えますね。

リアクティブプログラミング的なことをしたい場合は同じく Coroutines のChannelを使って非同期処理をラップしてあげると良いかもしれません。
それでは。

tweet Share