拝啓、RxJavaを使っている皆様へ
Coroutineはいいぞ
Coroutineこわくないよ
概要
現在Androidアプリでよく使われている技術といえば、RxJava(RxKotlin)です。
Retrofitを使ったAPI周りから、Listenerの代用など幅広く使えます。
一方で、明示的にDisposeを書いてあげないとメモリリークする危険などもあり、Rxちょっとな〜と思う場面も多いです。
さて、そんなRxに取って代わろうと言わんばかりに、Coroutine周りが進化し続けています。
今回の内容に関するところだけでいうと、
- Coroutines Flowの導入
- Retrofit Version2.6からのCoroutine対応 (suspendつけられるようになった
- 便利なCoroutineScopeの標準実装
などがあります。
これらを駆使すれば、今までやってたRx + Retrofitを使ったAPIの実装なども、Coroutineで簡単にかけるようになりました。
今回はおなじみのGithub APIをCoroutine Flow + Retrofitを使ったパターンで叩いてみます。
Coroutineまだまだ難しくて導入しずらいな、という人の助けになれば幸いです。
前提
CoroutineとKotlin Coroutine Flowは既に多くの方が解説をしてくださっていますので、
まずはそちらを読んでいただくことをおすすめします。
- Kotlin コルーチンを 理解しよう 2019
- 雰囲気で利用しないためのAndroidにおけるKotlin-Coroutineメモ
- Rx使いに送るKotlin Coroutine入門
- 5分でわかるKotlin Coroutines Flow
Flowとは、RxでいうところのObservableです。
Cold Streamで、collectを呼ぶことで動作開始します。
Sample
https://github.com/alpha2048/CoroutinesFlowTest
解説
Repository層 (API実装)
Rx
いままでRxを使ってGETを叩く場合は、以下のようなコードを書いていたケースが多いと思います。
Singleで返して、ViewModelなどでsubscribeするおなじみのパターンですね。
interface GithubApiInterface {
@GET("/search/repositories")
fun getGithubRepository(@Query("q") q: String,
@Query("page") page: Int): Single<RepoResponse>
}
class GithubRepository {
fun getRepositoryList(q: String, page: Int): Single<RepoResponse> {
val retrofit = Retrofit.Builder().略
val service = retrofit.create(GithubApiInterface::class.java)
return service
.getGithubRepository(q, page)
}
}
Coroutine
これをCoroutine対応します。
Retrofitは2.6 からsuspendがつけられるようになったので、suspendに直します。
そして、Responseを取得しつつ、Repository側でFlowを作って流すようにします。
interface GithubApiInterface {
@GET("/search/repositories")
suspend fun getGithubRepository(@Query("q") q: String,
@Query("page") page: Int): RepoResponse
}
class GithubRepository {
suspend fun getRepositoryList(q: String, page: Int): Flow<RepoResponse> = flow {
val retrofit = Retrofit.Builder().略
val service = retrofit.create(GithubApiInterface::class.java)
emit(service.getGithubRepository(q, page))
}.flowOn(Dispatchers.IO)
}
ViewModel層
Rx
こちらもいつものパターンですね
受け取った結果をSubscribeしてなにかします。また、Subjectなどのトリガーを発火させたりします。
ちゃんとDisposeしてあげないといけないですが、うっかり忘れてしまうことも少なくないです。
GithubRepository().getRepositoryList("Coroutine", 1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { t1, t2 ->
repositoryList.addAll(t1)
trigger.accept(Unit)
}
.addTo(compositeDisposable)
Coroutine
APIの取得をviewModelScopeで起動させることで、ViewModelのライフサイクルで動くようにします。
Disposeを書く必要がないので手間も少なくいい感じです。
また、イベントとしてBroadcastChannelを用意して、取得時に通知を出してあげます。
(BroadcastChannelは1対n用なので、今回の使い方では1対1用のChannelでもよい)
viewModelScope.launch(Dispatchers.Main) {
GithubRepository().getRepositoryList("Coroutine", 1).collect{
repoItems.addAll(it.items)
trigger.send(Unit)
}
}
Activity側
lifecycleScopeでイベントを受け取って、RecyclerViewの更新処理を行います。
こちらもDisposeを書く手間が減っています。
おわりに
Coroutineがここまで進化していたとなると、そろそろRxからの本格卒業を検討すべきかもしれないですね。
いずれはCoroutineを使った実装がデフォルトになっていくのかも?
2020/5/4 追記
Koinを使ったDIも追加しました
DIに興味がありましたらそちらも見てみてください〜〜
Koin
参考資料
わたしもコルーチンへの理解はまだまだなところがあるので、色々な資料を読みました。
コードはこちらで動かしてみつつ、下記の資料を参考に理解を深めてくださいませ。
解説
- Kotlin コルーチンを 理解しよう 2019
- 雰囲気で利用しないためのAndroidにおけるKotlin-Coroutineメモ
- Rx使いに送るKotlin Coroutine入門
- 5分でわかるKotlin Coroutines Flow
- DroidKaigi 2020 アプリでの学び【Kotlin Coroutines Flow 編】