15
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【Kotlin】そろそろRxを卒業して、Kotlin Coroutines Flow + RetrofitでAPIを叩いてみよう【Coroutine】

拝啓、RxJavaを使っている皆様へ

Coroutineはいいぞ
Coroutineこわくないよ

概要

現在Androidアプリでよく使われている技術といえば、RxJava(RxKotlin)です。
Retrofitを使ったAPI周りから、Listenerの代用など幅広く使えます。
一方で、明示的にDisposeを書いてあげないとメモリリークする危険などもあり、Rxちょっとな〜と思う場面も多いです。

さて、そんなRxに取って代わろうと言わんばかりに、Coroutine周りが進化し続けています。
今回の内容に関するところだけでいうと、

などがあります。
これらを駆使すれば、今までやってたRx + Retrofitを使ったAPIの実装なども、Coroutineで簡単にかけるようになりました。

今回はおなじみのGithub APIをCoroutine Flow + Retrofitを使ったパターンで叩いてみます。
Coroutineまだまだ難しくて導入しずらいな、という人の助けになれば幸いです。

前提

CoroutineとKotlin Coroutine 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

参考資料

わたしもコルーチンへの理解はまだまだなところがあるので、色々な資料を読みました。
コードはこちらで動かしてみつつ、下記の資料を参考に理解を深めてくださいませ。

解説

実装

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
15
Help us understand the problem. What are the problem?