历时4个月多,mvvm的第一个版本总算开发完成,心中的石头也算是落下了。想起去年的国庆节,7天假期没有迈出家门一步,抱着一本《kotlin 实战》书死磕,每每磕到深夜,在七天里面算是对kotlin这门语言入门了。学习了kotlin之后,为了学习Google爸爸最新的组件--jetpack,又跑到官方网站去死磕,前前后后不知道看了几遍文档,生怕漏了一点知识点。当然官方文档上介绍的知识都比较简单,为了深入的学习jetpack组件,又跑到国外的知名博客网站--medium学习大牛们分享的相关技术博客。
jetpack学习差不多两个多月吧,自认为自己学的挺好的。于是,为了巩固学习成果,动手写了这个mvvm框架--jade-mvvm。本文将简单介绍mvvm的整个结构,以及入门使用。
1. mvvm的分层
整个mvvm的分层参考于官方的介绍--应用架构指南,整个结构也符合如下图所示:
相比于官方推荐的结构,我在此基础扩展了两个点:
- View的职责分离。如果按照官方的推荐,View相关的操作必须在View层,这就意味着很多业务将耦合在View里面,可能会导致
Activity
和Fragment
,为了减轻Activity
和Fragment
的负担,同时为了将相关职责分离和整合,我在从MVP架构里面吸取了相关经验,将View拆分为了多个Presenter,每个Presenter只负责自己相关的业务即可。需要注意的是Presenter仍然属于View层。- 将repository整合成一个。按照官方的推荐,repository应该明确区分,比如说,从内存中加载数据的repository和从网络上加载数据的repository应该是不一样的。而我认为,这两个repository在上层的逻辑都是一样的,唯一的区别就是数据的来源,所以我在repository层做了统一,统一从request层去获取数据,由request层去决定数据的来源。
所以mvvm框架总结下来,便是如下的结构:
2. 基本使用
在mvvm框架里面,每个页面都有Activity+Fragment组成的,Activity本身不会承载很多的业务,所有业务都应该收敛在Fragment里面。
(1).Activity和Fragment的创建
每个Activity必须直接或者间接继承于BaseActivity,每个Fragment必须直接或者间接继承于BaseFragment。通常情况下,Activity只负责加载一个Fragment即可,当然也可以处理页面上的事情,比如说,window的feature和theme等。
下面展示一个简单的例子(可以从jade-mvvm获得完整的代码):
class MessageDetailActivity : BaseActivity() {
private val mMessageId by ExtraDelegate(MESSAGE_ID, 0)
override fun buildCurrentFragment() = MessageDetailFragment.newInstance()
override fun buildFragmentArguments() = Bundle().apply {
putInt(MESSAGE_ID, mMessageId)
}
companion object {
const val MESSAGE_ID = "MESSAGE_ID"
}
}
上面便是一个Activity的代码,它的工作主要加载一个Fragment,我们来看看对应的Fragment的代码:
class MessageDetailFragment : BaseFragment<MessageDetailViewModel>() {
private val mMessageId by ExtraDelegate(MessageDetailActivity.MESSAGE_ID, 0)
override fun getLayoutId() = R.layout.fragment_message_detail
override fun onCreateViewModel() =
ViewModelProvider(this, MessageDetailViewModel.Factory(mMessageId))[MessageDetailViewModel::class.java]
override fun onCreatePresenter() = Presenter().apply {
addPresenter(MessageDetailRefreshPresenter())
addPresenter(MessageDetailInitViewPresenter())
}
companion object {
fun newInstance() = MessageDetailFragment()
}
}
Fragment做的事情相对来说就比较多了,首先是创建一个自己的ViewModel,其次将一些相关业务抽到不同的Presenter里面。我这里将只是简单的定义了两个Presenter,其中:MessageDetailRefreshPresenter
用来负责刷新相关操作,MessageDetailInitViewPresenter
负责将加载回来的数据展示到View层。
需要特别注意的是:Presenter本身属于View层的一部分,所以请勿做一些非View层的事情,比如说请求数据。如果想要请求数据,应当通知ViewModel,让ViewModel去请求,Presenter此时只需要做一件事,监听对应的LiveData,等待数据更新在更新View层即可。
例如,MessageDetailRefreshPresenter
的代码将展示如何正确的请求数据:
class MessageDetailRefreshPresenter : Presenter() {
@Inject(Constant.VIEW_MODEL)
lateinit var mMessageDetailViewModel: MessageDetailViewModel
private val mRefreshLayout by lazy {
getRootView().findViewById<SwipeRefreshLayout>(R.id.refresh_layout)
}
override fun onBind() {
mMessageDetailViewModel.mLoadStatusLiveData.observe(getCurrentFragment()!!, Observer<LoadStatus> {
mRefreshLayout.isRefreshing = it == LoadStatus.LOADING_REFRESH
})
mRefreshLayout.setOnRefreshListener {
mMessageDetailViewModel.refresh()
}
mMessageDetailViewModel.trRefresh()
}
}
(2).如何正确创建一个ViewModel
前面已经说了, 每个Fragment需要一个ViewModel。所以在创建好一个Fragment之后,就需要定义一个与之对应的ViewModel。在mvvm框架里面,已经定义两种ViewModel:BaseViewModel
和BaseRecyclerViewModel
。其中BaseViewModel
用于普通Fragment;BaseRecyclerViewModel
用于带有RecyclerView的Fragment,推荐这类Fragment继承于RecyclerViewFragment
,其中BaseRecyclerViewModel
集成了jetpack组件里面的paging库,所以使用BaseRecyclerViewModel
,网络加载这块就不需要我们过多的关心。
为了介绍的简单,本文将以BaseViewModel
为例,简单介绍它的基本使用。如果想要了解BaseRecyclerViewModel
,可以看PositionViewModel和PositionViewModel.kt。
要想创建一个ViewModel,首先要继承于BaseViewModel
:
class MessageDetailViewModel(id: Int) : BaseViewModel<Message>(MessageDetailRepository(id)) {
class MessageDetailRepository(private val id: Int) : BaseRepository<Message>() {
override fun getRequest() = MessageDetailRequest(id)
}
class Factory(private val id: Int) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>) = MessageDetailViewModel(id) as T
}
}
创建一个ViewModel之后,我们还需要给ViewModel创建一个Repository
对象,这个Repository
对象的作用主要是加载数据,就如前文所说,Repository
里面主要从Request
层获取数据,这个数据可以从本地获取,也可以从网络上获取,这个不是ViewModel和Repository关心的。
在mvvm框架里面定义三个Repository
接口,分别是:
- ItemKeyedRepository:主要是跟
BaseItemKeyedDataSource
搭配使用。- ListPositionRepository:主要是跟
BasePositionDataSource
搭配使用。- Repository:通用的接口,只要不符合上面两种情况,均可以使用。
在ViewModel中,除了定义Repository
,每个实现者都可以按需定义很多的LiveData,用来达到的业务要求。关于LiveData的基本使用以及需要注意的事项,可以参考Google爸爸的博客:
3. 结语
jade-mvvm是我花了4个多月琢磨出来的,该框架完全遵从Google的建议。当然这个过程中,肯定有所不足的地方,欢迎大家积极提意见。接下来的日子,我将深入的分析jetpack库中的各个组件源码,像学习RecyclerView
源码一样,系统性的学习和分析底层源码。
网友评论