2. 模板,模板!
抛开事实事件倒灌的问题不谈, 再回头看看谷歌推荐的写法:
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { // Bind the visibility of the progressBar to the state // of isFetchingArticles. viewModel.uiState .map { it.isFetchingArticles } .distinctUntilChanged() .collect { progressBar.isVisible = it } } }
map操作符的作用是过滤uiState中的其他参数,distinctUntilChanged操作符是消抖,collect操作符是收集,非常的直观,非常的易懂。
但是,如果你对kotlin的Flow不太了解的话,你也许会写出下面的代码
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState .map { it.isFetchingArticles } .distinctUntilChanged() .collect { progressBar.isVisible = it } viewModel.uiState .map { it.xxx } .distinctUntilChanged() .collect { doSomeThing() } viewModel.uiState .map { it.yyy } .distinctUntilChanged() .collect { doSomeThing( } } }
看起来一切都没问题,继续收集其他属性,然后执行不同的操作,然而实际上等你真的把代码运行起来的时候,会发现除了第一个属性的收集是有相应的以外,其他的属性均收不到最新的值。
出现这个问题的原因是因为collect是suspend方法,他会阻塞下面的代码的执行,因此你需要给每一个collect都套一层launch方法,即开启多个协程,防止协程挂起导致下面的代码无法运行:
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch{ viewModel.uiState .map { it.isFetchingArticles } .distinctUntilChanged() .collect { progressBar.isVisible = it } } launch{ viewModel.uiState .map { it.xxx } .distinctUntilChanged() .collect { doSomeThing() } } launch{ viewModel.uiState .map { it.xxx } .distinctUntilChanged() .collect { doSomeThing() } } } }
恭喜你问题解决了,但是产生了一大堆模板代码,最核心的逻辑其实只包括2样:
- 要订阅的属性
- 获取到新值后的逻辑
谷歌的开发者文档对于入门MVI架构是非常合适的,但是谷歌只提供了非常基础的解决方案,并没有对这些逻辑做进一步的封装(这并不怪谷歌毕竟一个架构有非常多种实现方案,而且在一篇入门文章中阐述进阶的封装并不合适),因此我们需要封装来帮助我们解决掉这些难看的模板代码。