如何使用Flow和协程在循环中收集数据并等待完成后更新UI
问题根源
你代码的核心问题是for循环内通过launch启动的子协程为异步执行,外层协程不会等待这些子协程全部执行完成,就会直接调用updateUI,此时列表还未完成所有详情数据的填充。
解决方案
根据你的getScheduleDetails的行为不同,可以选择对应的方案:
方案1:async + awaitAll(推荐,getScheduleDetails仅返回一次结果时使用)
不需要维护共享可变集合,天然避免线程安全问题,代码更简洁:
viewModelScope.launch { useCases.getScheduleList().collect { scheduleList -> // 为每个日程创建异步请求详情的任务 val deferredList = scheduleList.map { schedule -> async { val detail = useCases.getScheduleDetails(schedule.code).first() newScheduleWithDetail(schedule, detail.body) } } // 等待所有异步请求完成,直接获取完整列表 val schedulesWithDetails = deferredList.awaitAll() updateUI(schedulesWithDetails) } }
方案2:Job + joinAll
如果需要保留launch的写法,可以存储所有子协程的Job对象,等待全部执行完成后再更新UI,注意要使用线程安全的集合存储结果:
viewModelScope.launch { useCases.getScheduleList().collect { scheduleList -> val jobList = mutableListOf<Job>() // 使用线程安全的列表避免并发添加数据出错 val schedulesWithDetails = Collections.synchronizedList(arrayListOf<ScheduleWithDetails>()) for (schedule in scheduleList) { val job = launch { val detail = useCases.getScheduleDetails(schedule.code).first() schedulesWithDetails.add(newScheduleWithDetail(schedule, detail.body)) } jobList.add(job) } // 等待所有子协程执行完成 jobList.joinAll() updateUI(schedulesWithDetails) } }
方案3:Flow组合(getScheduleDetails会持续发射更新时使用)
如果详情数据后续会动态变更,需要响应式更新UI,可以使用combine操作符合并所有详情Flow,任意详情更新都会自动触发UI刷新:
viewModelScope.launch { useCases.getScheduleList() .flatMapLatest { scheduleList -> val detailFlows = scheduleList.map { schedule -> useCases.getScheduleDetails(schedule.code) .map { detail -> newScheduleWithDetail(schedule, detail.body) } } combine(detailFlows) { it.toList() } } .collect { schedulesWithDetails -> updateUI(schedulesWithDetails) } }
内容的提问来源于stack exchange,提问作者Rafael J Silva
相关产品推荐
相关产品推荐

