Android ViewModel中如何让getOneName等待两个协程执行完毕?
Android ViewModel init块协程等待问题修复
问题描述
在ViewModel的init{}代码块中需要实现逻辑:
- 检查是否存在已下载的名称列表:
- 存在则随机选取一个名称;
- 不存在则先下载名称,存入
NameState列表后再随机选名称。
当前问题:init块内的getOneName()始终无法等待downloadNames()和updateStateWithNames()执行完成,导致选名称时列表为空。备注:这两个函数本身功能正常,按钮点击触发getOneName()可成功执行。
尝试过的代码
初始尝试
init{ launch{ getOneName() downloadNames() updateStateWithNames() } }
最新尝试
private val _nameUIState = mutableStateOf(NameState()) val nameUIState: State<NameState> = _nameUIState private var getNamesJob: Job? = null init{ if(_nameUIState.value.names.isNotEmpty()) { getOneName() }else { viewModelScope.launch(Dispatchers.IO) { val job = launch { downloadNames() // 函数内部也使用viewModelScope.launch updateStateWithNames() } job.join() getOneName() } } } private fun getOneName() { val idx = Random.nextInt(_nameUIState.value.names.size) val name = _nameUIState.value.names[idx] _nameUIState.value = nameUIState.value.copy(pickedName = name) } private fun downloadNames() { viewModelScope.launch { namesUseCases.downloadNames() } } private fun updateStateWithNames() { getNamesJob?.cancel() getNamesJob = namesUseCases.getAllNames() // 返回Flow类型 .onEach { namesList -> _nameUIState.value = nameUIState.value.copy(names = namesList) } .launchIn(viewModelScope) }
问题根源
downloadNames()异步嵌套:函数内部单独启动viewModelScope.launch,外层协程的job.join()仅能等待函数执行完毕(内部协程刚启动),无法等待下载操作完成。updateStateWithNames()Flow订阅异步:launchIn()是异步启动Flow订阅,外层协程无法感知Flow是否已发射数据并更新状态。
修复方案
1. 改造downloadNames()为挂起函数
去掉内部协程,直接调用挂起类型的用例方法,让外层协程能等待下载完成:
private suspend fun downloadNames() { namesUseCases.downloadNames() // 假设该方法本身是挂起函数 }
2. 改造updateStateWithNames()等待Flow首个结果
使用first()挂起函数获取Flow的第一个数据,确保状态更新后再继续执行,同时保留后续Flow的订阅:
private suspend fun updateStateWithNames() { getNamesJob?.cancel() // 等待获取第一个名称列表并更新UI状态 val initialNames = namesUseCases.getAllNames().first() _nameUIState.value = nameUIState.value.copy(names = initialNames) // 继续订阅Flow,监听后续数据更新 getNamesJob = namesUseCases.getAllNames() .onEach { newNames -> _nameUIState.value = nameUIState.value.copy(names = newNames) } .launchIn(viewModelScope) }
3. 调整init块协程逻辑
按顺序执行异步操作,确保所有准备完成后再调用getOneName():
init { viewModelScope.launch { if (_nameUIState.value.names.isEmpty()) { downloadNames() updateStateWithNames() } getOneName() } }
4. 给getOneName()添加空安全判断(可选)
避免极端情况下列表为空导致崩溃:
private fun getOneName() { val names = _nameUIState.value.names if (names.isNotEmpty()) { val idx = Random.nextInt(names.size) val name = names[idx] _nameUIState.value = nameUIState.value.copy(pickedName = name) } }
内容的提问来源于stack exchange,提问作者nguch
相关产品推荐
相关产品推荐

