本篇内容主要为UI层如何订阅改造之后的viewModel的事件流和状态流,
UI状态流的订阅
为什么要单独写一篇如何订阅Flow(流)的文章呢,首先我们回顾一下官方开发者文档中提供的推荐写法。(代码较多,每一行代码的作用已经用注释写明)
class NewsActivity : AppCompatActivity() { private val viewModel: NewsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { ... //开启协程 lifecycleScope.launch { //控制订阅的生命周期(在UI不可见时取消订阅避免性能浪费) repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState //只关注整个状态流中的某个元素 .map { it.isFetchingArticles } //防抖,防止其他元素更新导致自身更新 .distinctUntilChanged() //收集最终元素 .collect { progressBar.isVisible = it } } } } }
可见,仅仅是收集一个元素,我们就需要反复书写7行代码,除了我要收集什么元素和收集元素之后干什么以外,其余的代码均为模板代码,因此消除这些模板代码是第一业务,毕竟谁也不希望项目中存在一大堆复制粘贴的代码。
因此,我们的目标是实现一套这样的代码。
//绑定生命周期 viewModel.container.run{ uiStateFlow.collectState(某个lifecycleOwner) { //收集UiState中的某个元素 collectPartial(某个元素) { //做一些事情 } //收集UiState中的某个元素 collectPartial(某个元素) { //做一些事情 } //...收集其余的元素 } }
既然我们是在Flow上面新增逻辑,那么肯定是需要用到kotlin的扩展函数,因此我们给Flow增加一个扩展函数,同时指定必须是UiState的Flow(避免污染到其他非UiState Flow的代码)
fun <T : UiState> Flow<T>.collectState( //生命周期持有者,一般是Activity,Fragment之类的 lifecycleOwner: LifecycleOwner, //repeatOnLifecycle方法用 state: Lifecycle.State = Lifecycle.State.STARTED, //State收集器(关键元素),下文会谈到 action: StateCollector<T>.() -> Unit ) { //传递给State收集器,用于开启多个collectPartial()方法 StateCollector(this@collectState, lifecycleOwner, state).action() }
此处并没有给出StateCollector的源码,也许你会很好奇他的作用,其实它的作用并不复杂,只是将collectState()方法中的参数打包在一起,然后通过StateCollector来调用collectPartial()方法来收集UiState中的某个元素。
此时最大的问题来了,如何在方法中去指定一个Class中的某个元素呢?(下面提供一个data class供读者思考)
data class FriendUiState( //朋友列表 val friendBeanList: List<FriendBean> = emptyList(), //是否刷新中 val refreshing: Boolean = false, //是否加载更多 val loadMore: Boolean = false, //是否还有更多数据 val noMoreData: Boolean = false, ) : UiState
答案并不复杂,我们用反射即可,通过kt强大的反射机制,我们可以实现伪代码中的效果。关于kt的反射本文中并不会赘述,你可以通过掘金或者其他网站去自学kt的反射。在继续读下文之前,笔者假设你已经会了kt的反射(至少能看懂代码)
我们来看看刚才没有给出来的StateCollector的代码
class StateCollector<T : UiState>( private val flow: Flow<T>, private val lifecycleOwner: LifecycleOwner, private val state: Lifecycle.State, ) { fun <A> collectPartial( prop1: KProperty1<T, A>, action: (A) -> Unit ) { //lifecycleOwner利用开启协程 lifecycleOwner.lifecycleScope.launch { //控制订阅的生命周期 lifecycleOwner.repeatOnLifecycle(state) { //只关注整个状态流中的某个元素(重点!利用反射机制找出需要的元素) flow.map { prop1.get(it) } //防抖,防止其他元素更新导致自身更新 .distinctUntilChanged() //收集元素 .collect { partialState -> action(partialState) } } } }
可见,StateCollector的作用只有1点:消除模板代码。将需要反复重写的逻辑封装在一个收集器的内部,使用了StateCollector之后,我们重新回到最初的伪代码中。
//伪代码 viewModel.container.run{ uiStateFlow.collectState(某个lifecycleOwner) { //收集UiState中的某个元素 collectPartial(某个元素) { //做一些事情 } //收集UiState中的某个元素 collectPartial(某个元素) { //做一些事情 } //...收集其余的元素 } } //封装后的代码 viewModel.container.run { uiStateFlow.collectState(this@FriendsActivity2) { collectPartial(FriendUiState::friendBeanList) { //do someThing } collectPartial(FriendUiState::refreshing) { refreshing -> //do someThing } collectPartial(FriendUiState::loadMore) { loadMore -> //do someThing } collectPartial(FriendUiState::noMoreData) { noMoreData -> //do someThing } } }
经过封装后的代码减少了非常多的模板代码,在绑定了lifecycleOwner之后只需要关注两件事:
- 收集谁
- 收集之后干啥