はい。この記事の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で通信クラスを作るようにしようかね!