使用NetworkBoundResource时重复搜索同关键词协程始终活跃如何解决
问题根因
核心原因是 Room 返回的查询 Flow 是无限观察流:
- Room 针对表查询返回的 Flow 只要处于被收集状态,就会持续监听对应表的数据变更,只要数据发生更新就会发射新值,永远不会主动结束。
- 你实现的
networkBoundResource最后通过emitAll(flow)把 Room 对应的无限 Flow 暴露给上层,ViewModel 层收集这个流的协程(也就是searchJob对应的协程)会一直处于挂起等待新值的状态,所以searchJob.isActive永远返回true,触发重复搜索拦截逻辑。
解决方案
根据你的业务需求选择以下任意一种方案即可:
方案1:搜索场景不需要实时监听数据库变化,仅需单次搜索结果
在流的末端追加 take 操作符,拿到目标结果后主动结束流,让协程正常终止:
override fun searchPodcasts(term: String) = networkBoundResource( // 原有逻辑保持不变 ).flowOn(ioDispatcher) // 走网络请求会先后发射 Loading、Success/Error 两个事件,take(2) 拿到结果后立即结束流 .take(2)
如果你的业务不需要 Loading 状态,也可以替换为 take(1),仅取最终结果就结束流。
方案2:需要保持数据库变更实时同步,搜索页要随数据库更新自动刷新
这种场景下流需要长期保持活跃,不能强制结束,需要修改重复搜索的拦截逻辑:
新增变量标记上一次搜索的完成状态,不再仅依靠 searchJob.isActive 判断是否拦截:
// ViewModel 新增变量,记录上一次成功完成搜索的关键词 private var lastFinishedQuery: String? = null fun onSearchPodcast(term: String) { // 仅当同关键词、且上一次同关键词搜索还未拿到结果时才拦截 if (term == query && lastFinishedQuery != term && searchJob?.isActive == true) return query = term lastFinishedQuery = null _searchPodcasts.value = emptyList() showLive() searchJob?.cancel() searchJob = viewModelScope.launch { itunesRepo.searchPodcasts(term).collect { result -> Timber.d("Mtali : $result") when(result) { is Resource.Loading -> { spinner(true) if (!result.data.isNullOrEmpty()) { _searchPodcasts.value = result.data.toIPodcasts() } } is Resource.Error -> { // 标记本次搜索已结束 lastFinishedQuery = term message(result.error?.message) spinner(false) } is Resource.Success -> { // 标记本次搜索已结束 lastFinishedQuery = term if (result.data.isNullOrEmpty()) { message("Empty response") } else { _searchPodcasts.value = result.data.toIPodcasts() } spinner(false) } } } } }
内容的提问来源于stack exchange,提问作者Emmanuel Mtali
相关产品推荐
相关产品推荐

