LoginSignup
0

More than 1 year has passed since last update.

コルーチンとチャネル入門#7 進捗の表示

Last updated at Posted at 2022-06-17

ソース記事はこちら
いくつかのリポジトリの情報はかなり早くロードされるにもかかわらず、ユーザーはすべてのデータが一旦ロードされると結果リストを見るだけである。それまでは、ロード中アイコンが進捗を表示するが、どのコントリビューターがすでにロードされているのかという、現在の状態についての情報がない。
中間の結果を早めに見せ、それぞれのリポジトリのデータをロードした後で、すべてのコントリビューターを表示することができるかもしれない。
img
この機能を実装するには、中間の状態について呼び出されるコールバックとして、UIを更新するロジックを渡す必要がある。

suspend fun loadContributorsProgress(
    service: GitHubService,
    req: RequestData,
    suspend updateResults: (List<User>, completed: Boolean) -> Unit
) {
    // データのロード
    // 中間の状態について`updateResults`の呼び出し 
}

呼び出し側では、Mainスレッドから結果を更新するコールバックを渡す。

launch(Dispatchers.Default) {
    loadContributorsProgress(service, req) { users, completed ->
        withContext(Dispatchers.Main) {
            updateResults(users, startTime, completed)
        }
    }
}

updateResultsパラメータはloadContributorsProgress内でsuspendとして宣言されている。対応するラムダ引数の内部でsuspend関数である、withContextを呼ぶために、それが必要である。
updateResultコールバックは、すべてのローディングが完了し、結果が最終かどうかを意味する引数として、追加のBooleanパラメータを取る。

課題

中間結果を表示するloadContributorsProgress関数を実装すること(Request6Progress.ktファイル内で)。それは(Request4Suspend.ktからの)loadContributorsSuspend関数をもとにすること。並列性の無い、簡単なバージョンを使う。というのは次のセクションでこの解法に並列性を追加する方法を議論するからである。
留意すべきは、コントリビューターの中間結果リストは「集約された」状態で表示すべきであり、単にそれぞれのリポジトリにロードされたユーザーの一覧ではない。それぞれのユーザーごとの貢献合計数は、それぞれの新しいリポジトリがロードされるときに増加するはずである。

解法

ロードされたコントリビューターの中間結果の一覧は「集約された」状態で格納される必要がある。ユーザーの一覧を保存するallUsers変数を定義することができ、その後、それぞれの新しいリポジトリのコントリビューターがロードされた後で、それを更新する。

suspend fun loadContributorsProgress(
    service: GitHubService,
    req: RequestData,
    updateResults: suspend (List<User>, completed: Boolean) -> Unit
) {
    val repos = service
        .getOrgRepos(req.org)
        .also { logRepos(req, it) }
        .bodyList()

    var allUsers = emptyList<User>()
    for ((index, repo) in repos.withIndex()) {
        val users = service.getRepoContributors(req.org, repo.name)
            .also { logUsers(repo, it) }
            .bodyList()

        allUsers = (allUsers + users).aggregate()
        updateResults(allUsers, index == repos.lastIndex)
    }
}

updateResultsコールバックは、それぞれの要求が完了した後で、呼び出される。
img
今のところ、並列性は使われていない。このコードはシーケンシャルであり、同期化は必要ない。
要求を並列に送り、それぞれのリポジトリの応答を得た後で、中間結果を更新したいと思う。
img2
この解法にどのように並列性を追加するのか?チャネルがそれを解決する。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0