はい。この記事のCoroutine版です!
なにげにこの記事は悪手で、、、。
この記事のためにAPI基底クラスで色々融通効かせてみたんだけど、結果あまり必要なかったっていう。。。
Retrofit 2.6.0からCoroutineが対応したらしく神様JakeWhartonが作ったライブラリretrofit2-kotlin-coroutines-adapterが必要なかったっていうね。
このライブラリ使おうとしてたから、あんな形に・・・w
ってことで実装。
実装
依存
    // Coroutines
    def coroutines = "1.3.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
    // Retrofit
    def retrofit = "2.6.2"
    def moshi = "1.8.0"
    implementation "com.squareup.retrofit2:retrofit:$retrofit"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit"
    implementation "com.squareup.moshi:moshi:$moshi"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi"
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    implementation "com.squareup.okhttp3:okhttp:3.12.0"
    implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
    def lifecycle = "2.2.0-alpha04"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle"
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle"
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle"
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle"
    // Log
    implementation "com.jakewharton.timber:timber:4.7.1"lifecycle:lifecycleはalpha04じゃなくて現時点(2019年12月4日)でrc02が出てるっぽいけどlifecycle-common-java8のrc02が出てないっぽくてビルド通らないからあえて alpha04 にしてる。
viewModelを2.1.0以上にすると viewModelがCoroutineを持っててくれて実装が色々楽になるっぽい。
API基底
// API基底
abstract class ApiClient {
    abstract val baseUrl: String
    // Retrofitのインスタンス
    val retrofit: Retrofit by lazy {
        getBaseRetrofit()
            .baseUrl(baseUrl)
            .client(requireNotNull(getOkHttpClientBuilder(baseOkHttp).build()))
            .build()
            .let { requireNotNull(it) }
    }
    // 基底となるRetrofit
    private fun getBaseRetrofit(): Retrofit.Builder = Retrofit.Builder()
        .addConverterFactory(getConverterFactory())
    // 基底となるOkHttp
    private val baseOkHttp = getCommonHttpClientBuilder()
        .addInterceptor(Interceptor { chain ->
            val original = chain.request()
            //header
            val request = original.newBuilder()
                .header("Accept", "application/json")
                .method(original.method(), original.body())
                .build()
            return@Interceptor chain.proceed(request)
        })
        .addInterceptor { it.proceed(addUserAgent(it.request())) }
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
    // OkHttpを改変用(Interceptorを個別設定したいときとか)
    protected open fun getOkHttpClientBuilder(base: OkHttpClient.Builder): OkHttpClient.Builder =
        base
    // ConverterFactoryを改変用(Moshi,Gsonとか)
    protected open fun getConverterFactory(): Converter.Factory = MoshiConverterFactory.create(
        Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
    )
    private fun getCommonHttpClientBuilder(): OkHttpClient.Builder {
        return if (BuildConfig.DEBUG) {
            OkHttpClient.Builder()
                .addInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
                    Timber.tag("OkHttp").d(it)
                }).apply {
                    level = HttpLoggingInterceptor.Level.BODY
                })
                .addNetworkInterceptor(StethoInterceptor())
        } else {
            OkHttpClient.Builder()
        }
    }
    private fun addUserAgent(req: Request): Request {
        val agent = "AppName/" + BuildConfig.VERSION_NAME + " Android/" + BuildConfig.VERSION_NAME
        return req.newBuilder().header("User-Agent", agent).build()
    }
}前の記事とほとんど同じ。AdapterFactoryをRetrofitに渡していないだけ。
Service
// Coroutine用のサービス
interface CoroutineApiService {
    @GET("api")
    suspend fun getRandomUser(): RandomUser
}Retrofit特有のCallを書かなくて良い!
Rx特有のSingleとか書かなくて良い!
上で書いたライブラリ特有のDefferdを書かなくて良い!
Serviceの見た目がだいぶスッキリしたような気がする。
通信するのでsuspendで定義。
APIクラス
// Rxで通信するAPIクラス
class CoroutineApiClient : ApiClient() {
    override val baseUrl: String = "https://randomuser.me/"
    val service: CoroutineApiService = retrofit.create(CoroutineApiService::class.java)
}まぁ、継承してURL決めるだけです。
使ってみる
// ViewModel
class MainViewModel : ViewModel() {
    private val coroutineApiClient = CoroutineApiClient()
    fun getCoroutineDeRandomUser() {
        viewModelScope.launch {
            runCatching {
                val hoge1 = async { coroutineApiClient.service.getRandomUser() }
                val hoge2 = async { coroutineApiClient.service.getRandomUser() }
                Pair(hoge1.await(), hoge2.await())
            }.onSuccess {
                Log.d("hoge", "getCoroutineDeRandomUser $it")
            }.onFailure {
                Log.d("hoge", "onFailure getCoroutineDeRandomUser $it")
            }
        }
    }
    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel()
    }
}Rxよりスッキリかけるかな?っていう印象。
Coroutineのほうがお作法を覚えることが少ない気がするので、Rxわかんねぇよ!って人はこっちのほうが良いかもしれない。通信部に限り、、、。
個人的にRxのZipがすごく分かりづらいって思ってるんだけど、Coroutineなら自分でzipっぽいことできて、データを自由に加工できるから、並列処理をするときはCoroutineのほうが楽だなぁって感じ。
ただ、JobをcancelをExceptionが飛んでくるのはどうにかならないものか。どうにか出来るかもしれないけど調べてない。
先行処理を止めて、後続処理でやり直しとかしたいときにExceptionが飛んできたら面倒だなぁって思っただけなんだけど、、、。
とりあえず、通信周りはこの形がきれいに書けてる気がするし、今後1から作るプロジェクトあったら Coroutineで通信クラスを作るようにしようかね!



