给出以下组件
data class Account(val name: String)
data class GetAccountRequest(val name: String)
@Dao
interface AccountDao {
@Query("SELECT * FROM accounts ORDER BY name ASC")
fun all(): LiveData<List<Account>>
}
interface AccountOperations {
@GET("/foo/account")
suspend fun getAccount(@Body request: GetAccountRequest): Account
}
class AccountRepository(private val dao: AccountDao, private val api: AccountOperations) {
val accounts: LiveData<List<Account>> = dao.all()
suspend fun refresh(name: String) {
val account = api.getAccount(GetAccountRequest(name))
dao.insert(account)
}
}
我正在使用这些组件(由Room支持数据库和Retrofit API访问)的Android应用程序上工作。
在我的ViewModel中,我维护一个列出所有帐户的RecyclerView。我使用户可以手动刷新该列表。各自的(部分)ViewModel看起来像这样:
fun refresh() {
viewModelScope.launch {
repository.accounts.value?.forEach {
launch { repository.refresh(it.name) }
}
}
Timber.i("Done refreshing!")
}
我确实希望刷新同时更新所有帐户,这就是为什么我使用launch
的原因。我还决定在ViewModel中而不是在存储库中执行此操作,因为这将需要在存储库中启动新的协程。不建议每个this post使用哪个版本,因为存储库没有自然的生命周期。
上面的功能refresh
从UI调用,并在更新RecyclerView时显示刷新指示符。因此,一旦所有帐户更新完毕,我想停止使用该指标。
我上面的代码没有执行此操作,因为它将启动所有更新,然后在所有更新完成之前打印日志语句。结果,尽管仍有更新,刷新指示消失了。
所以,我的问题(最后)是:如何重构代码,使其并行运行所有更新,但是确保refresh
在所有更新完成之前不返回?
编辑#1
回到我要实现的目标:在更新视图时显示刷新指示器,我想到了以下内容(在ViewModel中更改了refresh
函数):
fun refresh() {
viewModelScope.launch {
try {
coroutineScope {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}
}
} catch (cause: CancellationException) {
throw cause
} catch (cause: Exception) {
Timber.e(cause)
} finally {
_refreshing.value = false
}
}
}
ViewModel在刷新时公开LiveData,并且片段可以观察它以显示或隐藏微调框。这似乎可以解决问题。但是,仍然感觉不合适,我感谢任何改进的解决方案。
答案 0 :(得分:0)
要await
执行所有并行的refresh()
操作,只需使用awaitAll()
:
coroutineScope.launch {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}.awaitAll()
_refreshing.value = false
}
此外,不建议用try/catch
包装协程。
您可以在此here上阅读更多内容。