歷時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
源碼一樣,系統性的學習和分析底層源碼。