ViewModel协程调用最佳实践:取消旧协程仅保留最新请求的实现方案
处理这类「仅保留最新请求、自动取消旧请求」的场景,Kotlin 协程生态下的公认最佳实践是使用 flatMapLatest(或 transformLatest)操作符配合触发器流实现,相比你提到的两种方案更简洁安全,也更符合流式编程的设计范式。
现有两种方案的缺陷
- 手动管理Job的方案:将协程生命周期控制逻辑放在UI层,不符合职责分离原则,页面重建、多入口触发请求时很容易出现漏取消、重复管理的问题,维护成本高。
- SharedFlow 方案:并没有真正取消旧请求,只是旧请求的结果不会最终影响UI,后台仍然会运行废弃的网络、数据库操作,属于无效的资源浪费,没有从根源解决协程泄漏问题。
最佳实践实现代码
class ScanViewModel( private val productRepository: ProductRepository, private val cartRepository: CartRepository ) : BaseViewModel<ScanUiState>(Initial) { // 搜索触发器流,存储当前需要查询的EAN码,null代表无查询请求 private val _searchEanFlow = MutableStateFlow<String?>(null) init { viewModelScope.launch { _searchEanFlow // 过滤无效的空请求 .filterNotNull() // 核心:新EAN到达时自动取消上一个未完成的请求协程 .flatMapLatest { ean -> setState(Loading) // 原有商品+购物车的组合逻辑保持不变 productRepository.getProductByEan(ean) .combine(cartRepository.getCart(), combineToGridItem()) } .collect { result -> when (result) { is Either.Left -> { sendEvent(Error(R.string.error_barcode_product_not_found, null)) setState(Initial) } is Either.Right -> { setState(ProductUpdated(result.right)) } } } } } fun fetchProduct(ean: String) { // UI层仅需要更新触发器流的值即可,无需管理协程 _searchEanFlow.value = ean } }
方案优势
- 自动取消旧请求:
flatMapLatest内置了新值到达时取消旧协程的逻辑,旧的网络、数据库操作会被直接中断,从根源避免后台协程泄漏和无效运行。 - 职责完全分离:所有逻辑都封装在ViewModel内部,UI层不需要关心协程管理,也不会受页面生命周期变化的影响。
- 功能拓展性强:如果需要添加扫描防抖,只需要在
filterNotNull后追加.debounce(300)即可实现指定时间内只处理最后一次请求,无需修改其他逻辑。 - 原有逻辑复用率高:商品和购物车的组合逻辑、结果处理逻辑和你原来的代码完全一致,不需要额外改造。
内容的提问来源于stack exchange,提问作者nilsi
相关产品推荐
相关产品推荐

