DataStore读取多Feature Flag流程阻塞:调用toList()无法完成
问题原因与解决方案
根本原因
DataStore的data属性返回的是无限流——只要存储的偏好数据发生变更,它就会持续发射新值,不会主动终止。你调用toList()时,这个函数会一直等待流完成(即流触发complete事件),但DataStore的流永远不会结束,导致协程卡在这一步,无法执行后续的_viewState.emit操作。
解决方案
方案1:修改现有流逻辑,只取单次值
针对你当前的getBoolEntries实现,只需要让每个key对应的流仅发射第一个值(也就是当前存储的最新值),这样整个合并后的流会在所有key的单次值都发射后终止,toList()就能正常拿到结果:
fun getBoolEntries(vararg keys: String): Flow<Pair<String, Boolean>> = flow { keys.forEach { emit(it) } }.onEach { Log.i("DBGG", "Key: $it") }.flatMapMerge { key -> // 取第一个值后就终止流,避免无限等待 getDataOrDefault(key, false).first().let { flowOf(Pair(key, it)) } }
ViewModel中的调用可以保留原逻辑,现在toList()会正常返回:
viewModelScope.launch { val flags = dataStoreRepository.getBoolEntries(KEY_1, KEY_2, KEY_3) .flowOn(Dispatchers.IO) .toList() _viewState.emit(ConfigState.Active(flags)) }
方案2:一次性读取所有key(更高效,推荐)
没必要为每个key单独订阅DataStore的流,直接一次性读取所有需要的偏好项,只订阅一次流即可,性能更优:
方式A:返回Flow(方便未来扩展监听)
fun getBoolEntries(vararg keys: String): Flow<List<Pair<String, Boolean>>> = dataStore.data .catch { exception -> if (exception is IOException) emit(emptyPreferences()) else throw exception } .map { pref -> keys.map { key -> val value = pref[booleanPreferencesKey(key)] ?: false Pair(key, value) } } .first() // 仅取单次数据,流终止
ViewModel调用:
viewModelScope.launch { val flags = dataStoreRepository.getBoolEntries(KEY_1, KEY_2, KEY_3) .flowOn(Dispatchers.IO) .first() _viewState.emit(ConfigState.Active(flags)) }
方式B:直接用suspend函数(最简洁,适合单次读取)
suspend fun getBoolEntries(vararg keys: String): List<Pair<String, Boolean>> = dataStore.data .catch { exception -> if (exception is IOException) emit(emptyPreferences()) else throw exception } .map { pref -> keys.map { key -> val value = pref[booleanPreferencesKey(key)] ?: false Pair(key, value) } } .first()
ViewModel调用:
viewModelScope.launch { val flags = dataStoreRepository.getBoolEntries(KEY_1, KEY_2, KEY_3) _viewState.emit(ConfigState.Active(flags)) }
未来扩展
如果之后需要监听实时变更,只需要去掉代码中的first()调用,让流保持无限状态,然后用collect替代toList()/first()来持续接收数据更新即可,代码可以平滑过渡。
内容的提问来源于stack exchange,提问作者shadox
相关产品推荐
相关产品推荐

