You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:11:15