[譯] 使用 Architecture Components 開發 MVVM 應用:MVP 開發者的實踐指南

原文:antonioleiva.com/mvvm-vs-mvp…
做者:antonioleiva.com/java

譯者說

最近在學習 MVVM 相關的知識,在最新一期的 KotlinWeekly 發現了這篇文章。做者經過按部就班的方式,向咱們闡述如何實現 MVVM,以及如何使用 Android Jetpack Components 組件來構建 MVVM 應用。讀完之後,收穫頗豐。爲了讓更多的開發者瞭解到 MVVM,我斗膽翻譯過來,這即是這篇文章的來由。英語渣渣,若有錯誤,還請指正。android

正文

導語

自從 Google 正式發佈了 Android Jetpack Components 架構組件,MVVM 已然成爲了 Android Apps 官宣的主流開發模式。我認爲是時候,提供一些行之有效的幫助,幫助使用 Mvp 模式的開發者來理解 MVVM 模式。git

若是您碰巧看到這篇博客,可是不知道怎麼在 Android 中使用 Mvp 模式,推薦您查看我以前寫的關於 Mvp 的博客。github

MVVM vs Mvp - 我須要去重構個人 App 嗎?

在至關長的一段時間內,Mvp 彷佛是用來 下降 UI 渲染業務邏輯 之間耦合的最受歡迎的開發模式。可是,如今咱們有了新的選擇。安全

許多開發者詢問我,是否應該逃避 Mvp,或者當開始新的項目如何設計架構。下面是一些想法:架構

  • Mvp 沒有消失。它仍然是徹底有效的開發模式,若是您以前使用它,也能夠接着使用。
  • MVVM 做爲新的開發模式,不必定更好。但谷歌所作的具體實施是頗有道理的,以前使用 MVP 的緣由是:它與 Android 框架很是吻合,而且上手難度不大。
  • 使用 Mvp 並不意味着,你不可使用 Android Jetpack Components 架構組件。可能 ViewModel 沒有多大的做用(它是 Presenter 的替代者),可是其餘組件能夠在項目中使用。
  • 您不須要當即重構您的 App,若是您對 Mvp 很是滿意,請繼續享受它。通常來講,最好保持一個安全,可靠的架構。而不是在項目中使用新的技術棧,畢竟重構是須要成本的。

MVVM 和 MVp 的差別

幸運的是,若是您以前熟悉 Mvp,學習 MVVM 將很是容易!在 Android 開發中,二者只有一點點的差別:框架

在 Mvp 中,PresenterView 經過 接口 聯繫。 在 MVVM 中,ViewModelView 經過 觀察者模式 通訊。mvvm

我知道,若是你曾閱讀過維基百科關於 MVVM 的定義。將會發現和我以前所說的徹底不符。可是在 Android 開發領域中,拋開 Databinding 不談,在我看來,這將是理解 MVVM 的最佳方式。ide

在不使用 Arch Components 的狀況下,從 MVp 遷移至 MVVM

我將使用 MVVM 來改造以前的 androidmvp 例子,MVVM 示例代碼請戳這裏 androidmvvm函數

我暫時不使用 Architecture Components,先本身實現。以後咱們就能夠清晰的認識到 Google 新推出的 Android Jetpack Components 是如何工做的,以及如何讓開發變得更加高效。

建立一個 Observable 類

當咱們使用 Observable 模式時,須要一個能夠觀察的類。該類將持有 Observer 和將發送給 Observer 的泛型類型的值, 以及當值發生改變,通知到 Observer

class Observable<T> {

    private var observers = emptyList<(T) -> Unit>()

    fun addObserver(observer: (T) -> Unit) {
        observers += observer
    }

    fun clearObservers() {
        observers = emptyList()
    }

    fun callObservers(newValue: T) {
        observers.forEach {
            it(newValue)
        }
    }
}
複製代碼

使用 States 來表示 UI 更改

因爲咱們如今沒法直接與 View 進行通訊,View 也不知道該怎麼顯示。我發現一個靈活的方式,經過一個 Model 類來表示 UI 狀態。

舉個栗子,若是咱們但願界面顯示一個進度條,咱們將發送一個 Loading 狀態,消費該狀態的方式徹底由視圖決定。

對於這種特殊狀況,我建立了一個 ScreenState 類,它接受一個表示視圖所需狀態的泛型類型。

每一個界面都有一些共同的狀態,例如 LoadingErroor。而後是每一個界面顯示的具體狀態。

可使用如下密閉類,來表示通用的 ScreenState

sealed class ScreenState<out T>{
    object Loading:ScreenState<Nothing>()
    class Render<T>(val renderState:T):ScreenState<T>()
}
複製代碼

對於特定狀態,咱們可能須要額外的定義。對於登錄狀態,枚舉類就足夠了。

enum class LoginState{
    Success,
    WrongUserName,
    WrongUserPassword
}
複製代碼

可是對於 MainState,咱們正在顯示列表和消息,枚舉類沒法提供足夠的支持,因此密閉類再次得到個人青睞(稍後會看到具體緣由)。

sealed class MainState{
    class ShowItems(val items:List<String>):MainState()
    class showMessage(val items:String):MainState()
}
複製代碼

將 Presenter 轉換爲 ViewModel

咱們再也不須要定義 View 接口,你能夠擺脫它。由於咱們將使用 Observable 替代。

以下示例:

val stateObservable = Observable<ScreenState<LoginState>>()
複製代碼

以後,當咱們想顯示進度條表示加載狀態時,只須要調用 LoadingStateObserver

fun validateCredentials(username: String, password: String) {
    stateObservable.callObservers(ScreenState.Loading)
    loginInteractor.login(username, password, this)
}
複製代碼

當登陸完成時,須要展現成功信息:

override fun onSuccess() {
 stateObservable.callObservers(ScreenState.Render(LoginState.Success))
}
複製代碼

老實說,登陸成功的狀態能夠用不一樣的方式實現,若是咱們想要更明確,可使用 LoginState.NavigateToMain 或者相似的方式進入首頁。

但這取決於更多因素,取決於應用程序架構。我會這樣作。

而後,在 ViewModelonDestroy() 中,咱們清除了 Observers,避免潛在的內存泄漏問題。

在 Activity 中使用 ViewModel

目前 Activity 還沒法充當 ViewModel 中 View 的角色,所以 觀察者模式 將會受到重用。

首先,初始化 ViewModel

private val viewModel = LoginViewModel(LoginInteractor())
複製代碼

以後,在 onCreate() 中觀察狀態,當狀態發生變化,將會調用 updateUI()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel.stateObservable.addObserver { updateUI() }
    }
複製代碼

在這裏,感謝密閉類和枚舉類。經過使用 when 表達式,一些變得如此簡單。我分兩步處理狀態:首先是通常狀態,而後是特定的 LoginState

第一個 when 表達式分支:顯示加載狀態的進度條。若是是其它特定狀態,須要調用另外的函數處理。

private fun updateUI(it: ScreenState<LoginState>) {
        when (it) {
            ScreenState.Loading -> progressbar.visibility = View.VISIBLE
            is ScreenState.Render -> processLoginState(it.renderState)
        }
    }
複製代碼

第二個 when 表達式分支:首先隱藏進度條(若是可見),若是是成功狀態,則進入首頁。若是是錯誤狀態,則提示相應的錯誤信息

private fun processLoginState(renderState: LoginState) {
        progressbar.visibility = View.GONE
        when (renderState) {
            LoginState.Success -> startActivity(Intent(this, MainActivity::class.java))
            LoginState.WrongUserName -> username.error = getString(R.string.username_error)
            LoginState.WrongUserPassword -> password.error = getString(R.string.password_error)
        }
    }
複製代碼

當點擊登陸按鈕,調用 ViewModel 中的 onLoginClicked() 進行操做。

private fun login() {
        viewModel.onLoginClicked(username.text.toString(), password.text.toString())
    }
複製代碼

而後,在 Activity 中的 onDestroy() 調用 ViewModelonDestroy() 釋放資源(這樣就能夠分離觀察者)。

override fun onDestroy() {
        viewModel.onDestroy()
        super.onDestroy()
    }
複製代碼

使用 Architecture Components 修改代碼

經過以前本身實現 MVVM 的 ViewModel,以便您能夠輕鬆的看到差別。到目前爲止,與 MVP 相比,MVVM 並無帶來更多的好處。

但也要一些不一樣,最重要的一點是您能夠忘記 Activity 的銷燬,因此您能夠脫離它的生命週期,隨時作你的工做。特別感謝 ViewModelLiveData。當 Activity 從新建立或者被銷燬時,您無需擔憂應用的崩潰。

這是工做原理:當 Activity 被從新建立,ViewModel 仍然存在,當 Activity 被永久殺死的時候,將會調用 ViewModelonCleared()

viewmodel-lifecycle.png

因爲 LiveData 也具備生命週期意識,所以它知道什麼時候跟 LifecycleOwner 創建和斷開聯繫。因此您無需關心它。

我並不打算深刻講解 Architecture Components 的工做原理(由於在官方的開發者指南中有更深入的解釋),因此讓咱們繼續探索實現 MVVM

在項目中使用 Architecture Components,須要添加如下依賴

implementation "android.arch.lifecycle:extensions:1.1.1"
複製代碼

若是您使用其餘組件,如:Room 。或者在 AndroidX 上使用這些組件,更多內容請參考 這裏

Architecture Components ViewModel

使用 ViewModel 很是簡單,你只須要繼承 ViewModel 便可。

class LoginViewModel(private val loginInteractor: LoginInteractor) : ViewModel()
複製代碼

刪除 onDestroy(),由於它再也不須要了。咱們能夠將釋放資源的代碼,轉移到 onCleared(),這樣咱們就不須要在 ActivityonCreate() 中添加觀察,onDestroy() 中移除觀察。就和咱們無需關心 onCleared() 的調用時機同樣。

override fun onCleared() {
        stateObservable.clearObservers()
        super.onCleared()
    }
複製代碼

如今,讓咱們回到 LoginActivity 中,建立一個具備延遲屬性的 ViewModel,在 onCreate() 中爲其分配值。

private lateinit var viewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel = ViewModelProviders.of(this)
            .get(LoginViewModel::class.java)
    }
複製代碼

ViewModel 不須要經過構造傳遞參數時,能夠按照上述方法實現。可是當咱們須要 ViewModel 經過構造傳遞參數時,則必須聲明一個工廠類。

class LoginViewModelFactory(private val loginInteractor: LoginInteractor) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T =
        LoginViewModel(loginInteractor) as T
}
複製代碼

Activity 中經過如下方式獲取 ViewModel 實例

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel = ViewModelProviders.of(this, LoginViewModelFactory(LoginInteractor()))
            .get(LoginViewModel::class.java)
    }
複製代碼

用 LiveData 替換 Observable

LiveData 能夠安全的替換咱們的 Observable 類,須要注意的一點是,LiveData 默認狀況是不可變的(您沒法改變其值)。

這很棒,由於咱們但願它是公共的,方便 Observer 能夠訂閱。但咱們不但願在其餘地方被修改。

可是,另外一方面,數據須要是可變的,否則咱們爲何會觀察它呢?所以,訣竅是使用一個私有的屬性,並提供一個公共的 getter

在 kotlin 中,它將是一個私有的屬性,和一個公共的 get() 屬性

private val _loginState: MutableLiveData<ScreenState<LoginState>> = MutableLiveData()
複製代碼
val loginState: LiveData<ScreenState<LoginState>>
    get() = _loginState
複製代碼

並且咱們也再也不須要 onCleared() 了,由於 LiveData 具備生命週期意識,它將在正確的時間中止觀察。

要觀察它,最簡潔的方式以下:

viewModel.loginState.observe(::getLifecycle, ::updateUI)
複製代碼

若是你不明白 函數引用,請查看我以前關於 函數引用 的文章。

updateUI() 須要 ScreenState 做爲參數,以便它適合 LiveData 的返回值。我能夠將它用做函數引用。

private fun updateUI(screenState: ScreenState<LoginState>?) {
    ...
}
複製代碼

MainViewModel 也不須要 onResume() 了,相反,咱們能夠重寫屬性的 getter,並在 LiveData 第一次觀察時,執行請求。

private lateinit var _mainState: MutableLiveData<ScreenState<MainState>>
 
val mainState: LiveData<ScreenState<MainState>>
    get() {
        if (!::_mainState.isInitialized) {
            _mainState = MutableLiveData()
            _mainState.value = ScreenState.Loading
            findItemsInteractor.findItems(::onItemsLoaded)
        }
        return _mainState
    }
複製代碼

MainActivity 的代碼和以前的相似。

viewModel.mainState.observe(::getLifecycle, ::updateUI)
複製代碼

注意

以前的代碼彷佛有點複雜,主要是由於使用了新的框架,當您瞭解它是如何工做的,一切將變得很是簡單。

確定有一些新的樣板代碼,例如 ViewModelFactory 和 獲取 ViewModel,或防止外部人員使用 LiveData 所定義的兩個屬性。我經過使用 Kotlin 的一些特性簡化了本文的一些內容,可使您的代碼更加簡潔,爲了簡單起見,我並不打算在這裏添加它們。

正如我在開頭所說的,您是否使用 MVVM 或者 MVP 徹底取決於您本身。若是您目前的架構使用 Mvp 運行良好,我認爲沒有重構的衝動,但瞭解 MVVM 的工做原理頗有意思。由於您早晚會須要它。

我認爲咱們仍在探索,在 Android 中使用 MVVM 和架構組件最優的解決方案,我相信個人方案並不完美。因此,請讓我聽到您心裏不一樣的聲音,我很樂意根據反饋更新文章。

您能夠在 GitHub 查看完整的代碼示例,(請 star 支持 )

相關文章
相關標籤/搜索