为什么读取 DataStore 的 Flow 时函数一直不返回?
只需要读取一次时,使用 first() 即可。当前函数一直不返回,是因为 collect 收到并处理一个值后,仍会继续等待 DataStore 的更新。
为什么赋值后仍然不返回?
collect 是挂起函数,会等待上游 Flow 完成。DataStore 的 data 用于持续观察数据,正常情况下不会在发出当前值后完成。
因此,原代码的执行过程是:
csrfToken.collect收到值,修改cookieString。- 收集继续等待后续更新。
- 后面的
sessionToken.collect和return都没有机会执行。
getCsrfToken() 有同样的问题:即使 token 已经赋值,执行流程仍停留在 collect 中。
此时协程持续挂起,不意味着线程被同步阻塞。把代码移到 Dispatchers.IO 也不会让 Flow 自动结束。
最小修改:将 collect 换成 first
如果每次请求只需要读取一次,可以复用现有的两个 Flow:
import kotlinx.coroutines.flow.first
suspend fun getCookiesAsString(): String {
val csrf = csrfToken.first()
val session = sessionToken.first()
return buildList {
if (csrf != "No Cookie") {
add("csrftoken=$csrf")
}
if (session != "No Cookie") {
add("sessionid=$session")
}
}.joinToString("; ")
}
suspend fun getCsrfToken(): String = csrfToken.first()
first() 获取第一个值后会结束本次收集,让后续代码继续执行。它仍可能挂起等待首次读取,但不会继续订阅之后的数据变化。
这种写法保留了现有 Flow 中的 IOException 处理逻辑。两个 Cookie 都不存在时,getCookiesAsString() 返回空字符串;getCsrfToken() 仍按原来的约定返回 "No Cookie",调用方需要处理这个缺失状态。
两个 Cookie 可以从同一份快照读取
上面的写法会分别读取两个 Flow。如果希望两个值来自同一份 Preferences 快照,可以在 CookieRepository 中读取一次:
import java.io.IOException
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
suspend fun getCookiesAsString(): String {
val preferences = dataStore.data
.catch { error ->
if (error is IOException) {
Log.e(TAG, "Error reading preferences.", error)
emit(emptyPreferences())
} else {
throw error
}
}
.first()
return buildList {
preferences[CSRF]?.let { add("csrftoken=$it") }
preferences[SESSION]?.let { add("sessionid=$it") }
}.joinToString("; ")
}
这里用键不存在表示缺少 Cookie,不需要依赖 "No Cookie" 占位字符串。
一次读取能保证两个值来自同一份快照。不过,原来的 saveCsrf() 和 saveSession() 是分别写入的。如果业务要求它们作为一组同时更新,还需要把两个赋值放到同一次 dataStore.edit 中。
其他注意事项
需要持续响应配置变化时,保留 collect;请求前读取一次凭据时,使用 first()。不要为了调用这些挂起函数而在主线程使用 runBlocking。
现有代码把读取时的 IOException 转成空 Preferences,因此读取失败也会表现为“没有 Cookie”。如果请求必须携带登录凭据,调用方应明确处理这种结果。
另外,header.second.split("=")[1] 会截断值中包含 = 的 Cookie。解析时至少应限制为首次分割,例如 split("=", limit = 2),并检查结果长度。HTTP 头名称的比较也应忽略大小写。这些是独立的解析问题,不是此次持续等待的原因。
备注:内容仅供参考。
