[Android] Retrofit(2.6.2) + Coroutine(コルーチン)でAPI通信を行う

はい。この記事の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-java8rc02が出てないっぽくてビルド通らないからあえて 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で通信クラスを作るようにしようかね!

コメント

タイトルとURLをコピーしました