mvvm - mvvm框架的入門使用 1. mvvm的分層 2. 基本使用 3. 結語

  歷時4個月多,mvvm的第一個版本總算開發完成,心中的石頭也算是落下了。想起去年的國慶節,7天假期沒有邁出家門一步,抱着一本《kotlin 實戰》書死磕,每每磕到深夜,在七天裏面算是對kotlin這門語言入門了。學習了kotlin之後,爲了學習Google爸爸最新的組件--jetpack,又跑到官方網站去死磕,前前後後不知道看了幾遍文檔,生怕漏了一點知識點。當然官方文檔上介紹的知識都比較簡單,爲了深入的學習jetpack組件,又跑到國外的知名博客網站--medium學習大牛們分享的相關技術博客。
  jetpack學習差不多兩個多月吧,自認爲自己學的挺好的。於是,爲了鞏固學習成果,動手寫了這個mvvm框架--jade-mvvm。本文將簡單介紹mvvm的整個結構,以及入門使用。

1. mvvm的分層

  整個mvvm的分層參考於官方的介紹--應用架構指南,整個結構也符合如下圖所示:


  相比於官方推薦的結構,我在此基礎擴展了兩個點:

  1. View的職責分離。如果按照官方的推薦,View相關的操作必須在View層,這就意味着很多業務將耦合在View裏面,可能會導致ActivityFragment,爲了減輕ActivityFragment的負擔,同時爲了將相關職責分離和整合,我在從MVP架構裏面吸取了相關經驗,將View拆分爲了多個Presenter,每個Presenter只負責自己相關的業務即可。需要注意的是Presenter仍然屬於View層。
  2. 將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:BaseViewModelBaseRecyclerViewModel。其中BaseViewModel用於普通Fragment;BaseRecyclerViewModel用於帶有RecyclerView的Fragment,推薦這類Fragment繼承於RecyclerViewFragment,其中BaseRecyclerViewModel集成了jetpack組件裏面的paging庫,所以使用BaseRecyclerViewModel,網絡加載這塊就不需要我們過多的關心。
  爲了介紹的簡單,本文將以BaseViewModel爲例,簡單介紹它的基本使用。如果想要了解BaseRecyclerViewModel,可以看PositionViewModelPositionViewModel.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接口,分別是:

  1. ItemKeyedRepository:主要是跟BaseItemKeyedDataSource搭配使用。
  2. ListPositionRepository:主要是跟BasePositionDataSource搭配使用。
  3. Repository:通用的接口,只要不符合上面兩種情況,均可以使用。

  在ViewModel中,除了定義Repository,每個實現者都可以按需定義很多的LiveData,用來達到的業務要求。關於LiveData的基本使用以及需要注意的事項,可以參考Google爸爸的博客:

  1. LiveData 概覽
  2. ViewModels and LiveData: Patterns + AntiPatterns
  3. LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData
  4. LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

3. 結語

  jade-mvvm是我花了4個多月琢磨出來的,該框架完全遵從Google的建議。當然這個過程中,肯定有所不足的地方,歡迎大家積極提意見。接下來的日子,我將深入的分析jetpack庫中的各個組件源碼,像學習RecyclerView源碼一樣,系統性的學習和分析底層源碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章