1、 Android Jetpack系列之MVVM使用及封装
2、Android Jetpack系列之MVVM使用及封装(续)
那么MVI
又是什么呢?看了一些关于MVI
的文章,大家都称MVI是(Model-View-Intent)
,其中Intent
称为意图(注意这里的Intent
并不是页面跳转时使用的Intent
),MVI本质上是在MVVM
的基础上将View
与ViewModel
之间的数据传递做了统一整合。
google
官方文档中并没有MVI
的说法,而是在之前的MVVM
架构基础上进行了升级,其主旨意思与MVI
很相近,为了保持一致,后续介绍的MVVM
升级版架构统一称之为MVI
架构。
关于LiveData
的缺点:
LiveData
的接收只能在主线程;LiveData
发送数据是一次性买卖,不能多次发送;LiveData
发送数据的线程是固定的,不能切换线程,setValue/postValue
本质上都是在主线程上发送的。当需要来回切换线程时,LiveData
就显得无能为力了。Flow
可以完美解决LiveData遇到的问题,既可以多次从上游发送数据,也可以灵活地切换线程,所以如果涉及到来回切线程,那么使用Flow
是更优解。关于Flow
的详细用法,感兴趣的同学可以参见:Android Kotlin之Flow数据流
注:如果项目中还没有切换到Kotlin
,依然可以使用LiveData
来发送数据;如果已经切换到Kotlin
,那么更推荐使用Flow
来发送数据。
还有一点区别,LiveData
在旧版架构中传递的是单个实体数据,即每个数据都会对应一个LiveData
,很显然,如果页面逻辑很复杂的话,会导致ViewModel
中的LiveData
膨胀;新版架构中通过Flow
发送的统一为UIState
了,UIState
本质上也是一个data类
,不同的是UIState
会把View
层相关的实体状态统一管控,这样在ViewModel
中只需要一个Flow
来统一交互即可。
新版架构中,提出了单向数据流来管理页面状态的概念:即数据的流向是固定的,整个数据流向是View -> ViewModel -> Model数据层 -> ViewModel获得数据 -> 根据UiState刷新View层
。其中,事件 Events
向上流动、状态 UiState
向下流动的。整体流程如下:
ViewModel
会存储并公开界面要使用的状态。界面状态是经过 ViewModel
转换的应用数据。ViewModel
发送用户事件通知。ViewModel
会处理用户操作并更新状态。官方给了一个点击书签的示例:
上面是UI界面
中添加书签的操作,点击之后成功添加书签,那么整个数据的流转过程如下:
单向数据流提高了代码的可读性及修改的便利性。单向数据流有以下好处:
kotlin
复制代码
class MViewModel : BaseViewModel
其中MviState
中定义的UIState
即是View
层相关的数据类,而MviSingleUiState
中定义的是一次性消费事件,如Toast
、跳转页面等,所以使用的Channel
来交互,在前面的文章中已经讲到了,这里不再重复。
相关接口:
kotlin
复制代码
interface IUiState //重复性事件 可以多次消费 interface ISingleUiState //一次性事件,不支持多次消费 object EmptySingleState : ISingleUiState //一次性事件,不支持多次消费 sealed class LoadUiState { data class Loading(var isShow: Boolean) : LoadUiState() object ShowMainView : LoadUiState() data class Error(val msg: String) : LoadUiState() }
LoadUiState
定义了页面加载的几种状态:正在加载Loading
、加载成功ShowMainView
、加载失败Error
,几种状态的使用与切换在BaseViewModel
中数据请求中进行了封装,具体使用可参考示例代码。ViewModel
初始化时可以直接传入EmptySingleState
。基类BaseViewModel
kotlin
复制代码
/** * ViewModel基类 * * @param UiState 重复性事件,View层可以多次接收并刷新 * @param SingleUiState 一次性事件,View层不支持多次消费 如弹Toast,导航Activity等 */ abstract class BaseViewModel
基类中StateFlow
的默认值是通过initUiState()
来定义的,并强制需要子类实现:
kotlin
复制代码
override fun initUiState(): MviState { return MviState(BannerUiState.INIT, DetailUiState.INIT) }
这样当一进入页面时就会在监听到这些初始化事件,并作出反应,如果不需要处理,可以直接略过。requestDataWithFlow
里封装了整个请求逻辑,
定义数据BaseData
类:
kotlin
复制代码
class BaseData
基类BaseRepository:
kotlin
复制代码
open class BaseRepository { suspend fun
基类中定义请求逻辑,子类中直接使用:
kotlin
复制代码
class WanRepository : BaseRepository() { val service = RetrofitUtil.getService(DrinkService::class.java) suspend fun requestWanData(drinkId: String): BaseData
> { return executeRequest { service.getBanner() } } suspend fun requestRankData(): BaseData
kotlin
复制代码
/** * MVI示例 */ class MviExampleActivity : BaseMviActivity() { private val mBtnQuest: Button by id(R.id.btn_request) private val mToolBar: Toolbar by id(R.id.toolbar) private val mContentView: ViewGroup by id(R.id.cl_content_view) private val mViewPager2: MVPager2 by id(R.id.mvp_pager2) private val mRvRank: RecyclerView by id(R.id.rv_view) private val mViewModel: MViewModel by viewModels() override fun getLayoutId(): Int { return R.layout.activity_wan_android_mvi } override fun initViews() { initToolBar(mToolBar, "Jetpack MVI", true, true, BaseActivity.TYPE_BLOG) mRvRank.layoutManager = GridLayoutManager(this, 2) } override fun initEvents() { registerEvent() mBtnQuest.setOnClickListener { mViewModel.showToast() //一次性消费 mViewModel.loadBannerData() mViewModel.loadDetailData() } } private fun registerEvent() { /** * Load加载事件 Loading、Error、ShowMainView */ mViewModel.loadUiStateFlow.flowWithLifecycle2(this) { state -> when (state) { is LoadUiState.Error -> mStatusViewUtil.showErrorView(state.msg) is LoadUiState.ShowMainView -> mStatusViewUtil.showMainView() is LoadUiState.Loading -> mStatusViewUtil.showLoadingView(state.isShow) } } /** * 一次性消费事件 */ mViewModel.sUiStateFlow.flowWithLifecycle2(this) { data -> showToast(data.message) } mViewModel.uiStateFlow.flowWithLifecycle2(this, prop1 = MviState::bannerUiState) { state -> when (state) { is BannerUiState.INIT -> {} is BannerUiState.SUCCESS -> { mViewPager2.visibility = View.VISIBLE mBtnQuest.visibility = View.GONE val imgs = mutableListOf
先回看下新版架构图,View->ViewModel
请求数据时通过events
来进行传递,可以如在ViewModel
中进行封装:
kotlin
复制代码
sealed class EVENT : IEvent { object Banner : EVENT() object Detail : EVENT() } override fun dispatchEvent(event: EVENT) { when (event) { EVENT.Banner -> { loadBannerData() } EVENT.Detail -> {loadDetailData() } }
那么View
层中可以如下调用:
scss
复制代码
mViewModel.dispatchEvent(EVENT.Banner) mViewModel.dispatchEvent(EVENT.Detail)
而在示例中在View
层发送数据请求时,并没有在ViewModel
中将请求进行封装,而是直接通过mViewModel.loadBannerData()
进行的请求,个人认为封装Event
的做法有点多余了。
升级版的MVI
架构相比于旧版MVVM
架构,规范性更好,约束性也更强。具体来说:
Flow
相比于LiveData
来说,能力更强,尤其当遇到来回切线程时;UIState
来集中管理页面的数据状态,从而ViewModel
中只需定义一个StateFlow
来管理即可,减少模板代码。同时定义UIState
也会带来副作用,即View
层没有diff
能力,会对每一次的事件进行全量更新,不过可以在View
层将UIState
里的内容细化监听来达到增量更新UI
的目的。但是并不是说新版的架构就一定适合你的项目,架构毕竟是一种规范,具体使用还需要见仁见智。
作者:_小马快跑_
链接:https://juejin.cn/post/7156471865542721566
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。