qingmei2 / MVVM-Architecture

The practice of MVVM + Jetpack architecture in Android.
1.83k stars 282 forks source link

关于状态管理的疑问 #52

Closed XWayne closed 3 years ago

XWayne commented 3 years ago

感谢大佬提供这么美妙的设计思路,想请问下大佬,关于状态管理这块,大佬最终采用了什么解决方案么?该MVVM项目,有协程和rxjava,我看到rxjava好像是类似于mvi文章讲到的,类似: data class MainViewState( val isLoading: Boolean, val error: Throwable? )

然后协程好像还是使用了传统的mvvm方式,每个view对于一个livedata,这么监听着改变状态。

使用mvi那种概念的话,有个疑问,就是每一次小小的变动,比如把viewA设为不可见,都把整个viewstate下发下去,这样相当于所有的view都重复无意义的更新了一遍,要是不想更新的话,就得让view层变得聪明起来,能够判断是哪里发生了变化,但这又增加了很大的累赘,请问大佬是怎么理解这状态管理的啊

qingmei2 commented 3 years ago

状态管理不是mvvm或者Android的专属,在前端这种思想和实践早已成熟,包括flutter的 Widget和 Jetpack 最新的 compose 组件,都是通过数据的变更对视图进行渲染,从编程思想上来说,这是趋势。

此外,你提到的「相当于所有的view都重复无意义的更新了一遍」其实性能影响上来说,并没有你想象的那么大,比如对于列表的渲染可以这样做:

if (recyclerView.adapter != null ) {
   // 如果数据有变动,增量更新列表
   // 如果数据没变动,列表甚至不需要任何更新
} else {
  // 初始化列表
}

继续延伸下去就太多了哈,可以多尝试一下。

实际上把整个页面UI都作为一个Model是最合理的,但是在实践中这太理想化了,而且我们 不可能 要求团队所有成员都有这样的业务模型的组织能力,只需要把握好一个度,这个项目的结构其实并不一定适合所有同行,而通过学习和借鉴,找到符合自己(团队)的方案才是最好的。

XWayne commented 3 years ago

感谢大佬哈,这个「度」着实有点难把握哈。 我在项目开发里,只是使用传统livedata方式一对一发送。现在是在自己开发个小项目学习新技术应用哈。

看到大佬的回复,我可能进入了一个误区,我持有的是针对功能的状态,而不是ui可以直接渲染的状态(可能有点钻了牛角尖哈): 假设有个删除功能 data class MainViewState( ... val deleteLoading:Boolean, val deleteError:Throwable?, ... ) 如果我想删除列表的一个item,开始删除时发送含有[deleteLoading = true] 的model事件从而展示弹窗,然后删除失败后我就发送了 [deleteLoading = false,deleteError:=xxxError]事件取消了弹框并弹出请求错误的吐司。 但是在吐司之后,我没有发送[deleteLoading = false,deleteError:=null]事件从而清除掉失败状态,因为我把它当成一个功能的状态(请求失败)来保存,并且是在view层加多了额外的判断是否需要展示(仔细想想,可能会在屏幕旋转下,还是又会弹出删除失败的吐司)。 按这种数据的变更对视图进行渲染,其实应该直接发多一次[deleteLoading = false,deleteError:=null]事件从而清除掉失败状态,view层拿到了直接渲染,其他view顶多再渲染一次,我这样理解对么哈?

qingmei2 commented 3 years ago

@XWayne

这是个好问题,我猜想你想叙述的是,如何对页面常驻的state,和一次性事件类型的event进行使用上的区分,最好保证代码的 可读性简单好用(因为页面返回或者配置发生变更都会对后者的事件进行再一次处理,导致UI显示错误)。

实际上关于这个问题一直都有争论,google官方推荐的是 SingleEventLiveData 这种方式,在我的mvi系列译文中,mosby的原作者又对这种方式进行了批判,具体好坏见仁见智,我个人其实倾向后者,也就是你说的「多发送一次事件清除掉状态」。

但是坏处(我觉得是坏处而非缺点)显而易见,这种方式增加了代码的复杂度,降低了部分的可读性,因为正常的实现中,这个新的事件是没必要发送的。

所以,具体场景具体权衡,对于这种事件类型比较多的页面,可能前者的实现方式更好,而对于少或者没有时间类型的页面,我更倾向后者的实现。我也建议,将特殊事件类型(比如Throwable)和常规的页面UI Model 进行分开,交给不同liveData存储,这样,配置发生变更,或者从下一级页面返回当前页面时,UI能够正常通过Model进行渲染,而事件不再渲染。

说到底好像又回到了google推荐的SingleEventLiveData方式了? :smile:

XWayne commented 3 years ago

mosby作者提到的争论issue看了下,到目前还没关闭😄 想要有个满足一切的驱动方式,着实不可能。 目前是想着,对于复杂的,拆分成按功能分类的rxjava的subject发送(没接入协程,还是用着rxjava),像最后推荐的一样,最后还是有点回归SingleEventLiveData这种单独对应类型了。 重点是要保证 界面的正确性和简单易用性 ,两者之间相互借鉴下哈。 感谢大佬的答疑哈,先关闭问题哈⭐⭐接着我再多多实践尝试下,问题思考才比较全面深入点🤝🤝 有时候敲多点代码,问题就越想越顺了哈。