アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Retrofit + Kotlin Coroutines

Android版いこーよを担当しているhondaです。

Android版いこーよでは通信処理でKotlin Coroutines(以下、コルーチン)を使い始めました。
コルーチンは簡単に言うと「スレッドインスタンスのようなものであり、中断可能な計算インスタンス」と言えます。
また、コルーチンは「中断されている最中にスレッドをブロックしない」という特徴を持っています。
なので、通信処理や時間がかかる処理などを非同期で実行でき、かつシンプルなコードで実装できます。

コルーチンについては前回のエントリーでも簡単にですが紹介しました。
tech.actindi.net

いこーよで通信処理にRetrofitを使っています。
Retrofitはバージョン2.5までコルーチンには未対応でした。
よって、バージョン2.5までのRetrofitでコルーチンを使うためには前回のエントリーで書いたようにこちらのサードパーティーのライブラリを使用したり、自前でDeferredを使って対応する必要があります。

最新バージョン2.6でコルーチンに対応したことによって、サードパーティや独自実装なしでコルーチンを使って非同期通信処理を書けるようになりました。

今回はGitHubのSearchAPIに対してのGETリクエストする処理でコルーチンを使わない場合と使った場合でコードの比較をしたいと思います。

GitHubAPIとのインターフェースとなるメソッドの定義

interface GitHubServiceInterface {
    //コルーチン使わないコード
    @GET("search/repositories")
    fun getSearchedRepositories(
        @Query(value = "q") query: String,
        @Query(value = "sort") sort: String,
        @Query(value = "order") order: String
    ): Call<GitHubRepositoriesData>

    // コルーチンを使うコード
    @GET("search/repositories")
    suspend fun getSearchedRepositoriesWithCoroutines(
        @Query(value = "q") query: String,
        @Query(value = "sort") sort: String,
        @Query(value = "order") order: String
    ): GitHubRepositoriesData
}

ここでの違いはsuspendと戻り値です。
コルーチンを使わないコートではリクエストの結果が返ってくるCallオブジェクトを返します
コルーチンを使ったコードではインターフェースとなるメソッドにsuspend修飾子が付き、Callオブジェクトではなく、GETリクエストで取得できるデータのオブジェクトを返します。
suspend修飾子はそのメソッドがコルーチンを中断するかもしれないことを表します。
今回の場合はRetrofit側がGETリクエストを開始してからレスポンスが返ってくるまでコルーチンを中断するため、suspend修飾子を付ける必要があります。

GitHubServiceInterfaceのインスタンスの生成

val gitHubService = Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build()
    .create(GitHubServiceInterface::class.java)
}

ここはとくにコードの違いはありません。

GETリクエストメソッド

コルーチンを使わないコード

   fun fetchSearchedRepositories(
        query: String,
        sort: String,
        order: String,
        onResponse: (response: GitHubRepositoriesData?) -> Unit,
        onFailure: (throwable: Throwable) -> Unit
    ) {
        gitHubService
            .getSearchedRepositories(query = query, sort = sort, order = order)
            .enqueue(
                object : Callback<GitHubRepositoriesData> {
                    override fun onResponse(
                        call: Call<GitHubRepositoriesData>,
                        response: Response<GitHubRepositoriesData>
                    ) {
                        onResponse(response.body())
                    }

                    override fun onFailure(call: Call<GitHubRepositoriesData>, t: Throwable) {
                        onFailure(t)
                    }
                }
            )
    }

コルーチンを使ったコード

   suspend fun fetchSearchedRepositoriesByCoroutines(
        query: String,
        sort: String,
        order: String
    ): GitHubRepositoriesData {
        return gitHubService.getSearchedRepositoriesWithCoroutines(query = query, sort = sort, order = order)
    }

ここでの違いはコールバックと再び出てきたsuspend修飾子です。
コルーチンを使わないコードではGETリクエストで得られるデータやGETリクエストが失敗時の結果をコールバックで受け取る必要があります
コルーチンを使うコードではまずGETリクエストを行うメソッドはsuspend修飾子が付いています。
これは前述したGitHubEndpointInterface.getSearchedRepositoriesByCoroutines()にsuspend修飾子が付いているため、それを呼び出すfetchSearchedRepositoriesByCoroutines()にもsuspend修飾子を付ける必要があるからです。
GETリクエストで得られるデータはメソッドの戻り値として取得することができます。
コルーチンを使ったコードのほうが圧倒的にシンプルに書けることがわかります。

取得したデータを画面に表示

コルーチンを使わないコード

    fun searchGitHubRepository(query: String) {
        fetchSearchedRepositories(
            query = query,
            sort = "stars",
            order = "desc",
            onResponse = {
                // 検索したGitHubリポジトリを画面に表示する
            },
            onFailure = {
                // リクエスト失敗時の処理を行う
            }
        )
    }

コルーチンを使ったコード

    val coroutineScope = CoroutineScope(context = Dispatchers.Main)
    fun searchGitHubRepositoryByCoroutines(query: String) {
        coroutineScope.launch {
            try {
                val gitHubRepositoriesData = fetchSearchedRepositoriesByCoroutines(
                    query = query,
                    sort = "stars",
                    order = "desc"
                )
                // 検索したGitHubリポジトリを画面に表示する
            } catch (e: HttpException) {
                // リクエスト失敗時の処理を行う
            }
        }
    }

ここでの違いはcoroutineScope.launchです。
CoroutineScope#launchメソッドでコルーチンが生成、実行されます。*1
コルーチンを使っているコードではその生成、実行されたコルーチンの中でfetchSearchedRepositoriesByCoroutinesを呼び出して結果を戻り値として取得することが出来ます。
つまり、非同期通信処理を通常のメソッド呼び出しのように「同期的な」書き方で実装できるため、見通しが良くなることがおわかりになると思います。

まとめ

Retrofitがコルーチンに対応したことで、Retrofitを使った非同期通信処理がとてもシンプルに書けるようになりました。
また、コルーチンに特性により、意図しないUIスレッドのブロックを防ぐことも出来ます。
そして、シンプルに書けることによって、よりアプリとして本質的な機能実装にフォーカスすることができるようになりました。
今後もコルーチンをうまく使って、Android版いこーよアプリをグロースさせていきます!

現在、コルーチンを使ってAndroid版いこーよを一緒にグロースしてくれる仲間を募集しています!
少しでもご興味ある方!以下のリンクからご応募よろしくおねがいします!

actindi.net www.wantedly.com

*1:ちなみにCoroutineScopeを実際のAndroidアプリで使用する際はAndroid Architecture ComponentsでのViewModelなどで生成し、Androidのライフサイクルにリンクするように使用するのが一番多いかと思います。https://developer.android.com/topic/libraries/architecture/coroutines