ソース記事はこちら
いくつかのリポジトリの情報はかなり早くロードされるにもかかわらず、ユーザーはすべてのデータが一旦ロードされると結果リストを見るだけである。それまでは、ロード中アイコンが進捗を表示するが、どのコントリビューターがすでにロードされているのかという、現在の状態についての情報がない。
中間の結果を早めに見せ、それぞれのリポジトリのデータをロードした後で、すべてのコントリビューターを表示することができるかもしれない。
この機能を実装するには、中間の状態について呼び出されるコールバックとして、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
コールバックは、それぞれの要求が完了した後で、呼び出される。
今のところ、並列性は使われていない。このコードはシーケンシャルであり、同期化は必要ない。
要求を並列に送り、それぞれのリポジトリの応答を得た後で、中間結果を更新したいと思う。
この解法にどのように並列性を追加するのか?チャネルがそれを解決する。