原文:https://antonioleiva.com/mvvm-vs-mvp/
作者:https://antonioleiva.com/
譯者說(shuō)
最近在學(xué)習(xí) MVVM 相關(guān)的知識(shí)粤攒,在最新一期的 KotlinWeekly 發(fā)現(xiàn)了這篇文章。作者通過(guò)循序漸進(jìn)的方式囱持,向我們闡述如何實(shí)現(xiàn) MVVM夯接,以及如何使用 Android Jetpack Components 組件來(lái)構(gòu)建 MVVM 應(yīng)用。讀完以后纷妆,收獲頗豐盔几。為了讓更多的開(kāi)發(fā)者了解到 MVVM,我斗膽翻譯過(guò)來(lái)掩幢,這便是這篇文章的來(lái)由逊拍。英語(yǔ)渣渣,如有錯(cuò)誤际邻,還請(qǐng)指正芯丧。
正文
導(dǎo)語(yǔ)
自從 Google 正式發(fā)布了 Android Jetpack Components 架構(gòu)組件,MVVM 已然成為了 Android Apps 官宣的主流開(kāi)發(fā)模式世曾。我認(rèn)為是時(shí)候注整,提供一些行之有效的幫助,幫助使用 Mvp 模式的開(kāi)發(fā)者來(lái)理解 MVVM 模式。
如果您碰巧看到這篇博客肿轨,但是不知道怎么在 Android 中使用 Mvp 模式,推薦您查看我之前寫(xiě)的關(guān)于 Mvp 的博客蕊程。
MVVM vs Mvp - 我需要去重構(gòu)我的 App 嗎椒袍?
在相當(dāng)長(zhǎng)的一段時(shí)間內(nèi),Mvp 似乎是用來(lái) 降低 UI 渲染 和 業(yè)務(wù)邏輯 之間耦合的最受歡迎的開(kāi)發(fā)模式藻茂。但是驹暑,現(xiàn)在我們有了新的選擇。
許多開(kāi)發(fā)者詢(xún)問(wèn)我辨赐,是否應(yīng)該逃避 Mvp优俘,或者當(dāng)開(kāi)始新的項(xiàng)目如何設(shè)計(jì)架構(gòu)。下面是一些想法:
- Mvp 沒(méi)有消失掀序。它仍然是完全有效的開(kāi)發(fā)模式帆焕,如果您之前使用它,也可以接著使用不恭。
- MVVM 作為新的開(kāi)發(fā)模式叶雹,不一定更好。但谷歌所做的具體實(shí)施是很有道理的换吧,之前使用 MVP 的原因是:它與 Android 框架非常吻合折晦,并且上手難度不大。
- 使用 Mvp 并不意味著沾瓦,你不可以使用 Android Jetpack Components 架構(gòu)組件满着。可能
ViewModel
沒(méi)有多大的作用(它是 Presenter 的替代者)贯莺,但是其他組件可以在項(xiàng)目中使用风喇。 - 您不需要立即重構(gòu)您的 App,如果您對(duì) Mvp 非常滿(mǎn)意乖篷,請(qǐng)繼續(xù)享受它响驴。一般來(lái)說(shuō),最好保持一個(gè)安全撕蔼,可靠的架構(gòu)豁鲤。而不是在項(xiàng)目中使用新的技術(shù)棧,畢竟重構(gòu)是需要成本的鲸沮。
MVVM 和 MVp 的差異
幸運(yùn)的是琳骡,如果您之前熟悉 Mvp,學(xué)習(xí) MVVM 將非常容易讼溺!在 Android 開(kāi)發(fā)中楣号,兩者只有一點(diǎn)點(diǎn)的差異:
在 Mvp 中,Presenter 和 View 通過(guò) 接口 聯(lián)系。
在 MVVM 中炫狱,ViewModel 和 View 通過(guò) 觀察者模式 通信藻懒。
我知道,如果你曾閱讀過(guò)維基百科關(guān)于 MVVM 的定義视译。將會(huì)發(fā)現(xiàn)和我之前所說(shuō)的完全不符嬉荆。但是在 Android 開(kāi)發(fā)領(lǐng)域中,拋開(kāi) Databinding
不談酷含,在我看來(lái)鄙早,這將是理解 MVVM 的最佳方式。
在不使用 Arch Components 的情況下椅亚,從 MVp 遷移至 MVVM
我將使用 MVVM 來(lái)改造之前的 androidmvp 例子限番,MVVM 示例代碼請(qǐng)戳這里 androidmvvm。
我暫時(shí)不使用 Architecture Components呀舔,先自己實(shí)現(xiàn)弥虐。之后我們就可以清晰的認(rèn)識(shí)到 Google 新推出的 Android Jetpack Components 是如何工作的,以及如何讓開(kāi)發(fā)變得更加高效别威。
創(chuàng)建一個(gè) Observable 類(lèi)
當(dāng)我們使用 Observable 模式時(shí)躯舔,需要一個(gè)可以觀察的類(lèi)。該類(lèi)將持有 Observer 和將發(fā)送給 Observer 的泛型類(lèi)型的值省古, 以及當(dāng)值發(fā)生改變粥庄,通知到 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 來(lái)表示 UI 更改
由于我們現(xiàn)在無(wú)法直接與 View 進(jìn)行通信豺妓,View 也不知道該怎么顯示惜互。我發(fā)現(xiàn)一個(gè)靈活的方式,通過(guò)一個(gè) Model 類(lèi)來(lái)表示 UI 狀態(tài)琳拭。
舉個(gè)栗子训堆,如果我們希望界面顯示一個(gè)進(jìn)度條,我們將發(fā)送一個(gè) Loading 狀態(tài)白嘁,消費(fèi)該狀態(tài)的方式完全由視圖決定坑鱼。
對(duì)于這種特殊情況,我創(chuàng)建了一個(gè) ScreenState 類(lèi)絮缅,它接受一個(gè)表示視圖所需狀態(tài)的泛型類(lèi)型鲁沥。
每個(gè)界面都有一些共同的狀態(tài),例如 Loading耕魄,Erroor画恰。然后是每個(gè)界面顯示的具體狀態(tài)。
可以使用以下密閉類(lèi)吸奴,來(lái)表示通用的 ScreenState
sealed class ScreenState<out T>{
object Loading:ScreenState<Nothing>()
class Render<T>(val renderState:T):ScreenState<T>()
}
對(duì)于特定狀態(tài)允扇,我們可能需要額外的定義缠局。對(duì)于登陸狀態(tài),枚舉類(lèi)就足夠了考润。
enum class LoginState{
Success,
WrongUserName,
WrongUserPassword
}
但是對(duì)于 MainState狭园,我們正在顯示列表和消息,枚舉類(lèi)無(wú)法提供足夠的支持额划,所以密閉類(lèi)再次獲得我的青睞(稍后會(huì)看到具體原因)妙啃。
sealed class MainState{
class ShowItems(val items:List<String>):MainState()
class showMessage(val items:String):MainState()
}
將 Presenter 轉(zhuǎn)換為 ViewModel
我們不再需要定義 View 接口,你可以擺脫它俊戳。因?yàn)槲覀儗⑹褂?Observable 替代。
如下示例:
val stateObservable = Observable<ScreenState<LoginState>>()
之后馆匿,當(dāng)我們想顯示進(jìn)度條表示加載狀態(tài)時(shí)抑胎,只需要調(diào)用 LoadingState 的 Observer。
fun validateCredentials(username: String, password: String) {
stateObservable.callObservers(ScreenState.Loading)
loginInteractor.login(username, password, this)
}
當(dāng)?shù)卿浲瓿蓵r(shí)渐北,需要展示成功信息:
override fun onSuccess() {
stateObservable.callObservers(ScreenState.Render(LoginState.Success))
}
老實(shí)說(shuō)阿逃,登錄成功的狀態(tài)可以用不同的方式實(shí)現(xiàn),如果我們想要更明確赃蛛,可以使用 LoginState.NavigateToMain
或者類(lèi)似的方式進(jìn)入首頁(yè)恃锉。
但這取決于更多因素,取決于應(yīng)用程序架構(gòu)呕臂。我會(huì)這樣做破托。
然后,在 ViewModel 的 onDestroy()
中歧蒋,我們清除了 Observers土砂,避免潛在的內(nèi)存泄漏問(wèn)題。
在 Activity 中使用 ViewModel
目前 Activity
還無(wú)法充當(dāng) ViewModel 中 View 的角色谜洽,因此 觀察者模式 將會(huì)受到重用萝映。
首先,初始化 ViewModel
private val viewModel = LoginViewModel(LoginInteractor())
之后阐虚,在 onCreate()
中觀察狀態(tài)序臂,當(dāng)狀態(tài)發(fā)生變化,將會(huì)調(diào)用 updateUI()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
viewModel.stateObservable.addObserver { updateUI() }
}
在這里实束,感謝密閉類(lèi)和枚舉類(lèi)奥秆。通過(guò)使用 when
表達(dá)式,一些變得如此簡(jiǎn)單磕洪。我分兩步處理狀態(tài):首先是一般狀態(tài)吭练,然后是特定的 LoginState
。
第一個(gè) when
表達(dá)式分支:顯示加載狀態(tài)的進(jìn)度條析显。如果是其它特定狀態(tài)鲫咽,需要調(diào)用另外的函數(shù)處理签赃。
private fun updateUI(it: ScreenState<LoginState>) {
when (it) {
ScreenState.Loading -> progressbar.visibility = View.VISIBLE
is ScreenState.Render -> processLoginState(it.renderState)
}
}
第二個(gè) when
表達(dá)式分支:首先隱藏進(jìn)度條(如果可見(jiàn)),如果是成功狀態(tài)分尸,則進(jìn)入首頁(yè)锦聊。如果是錯(cuò)誤狀態(tài),則提示相應(yīng)的錯(cuò)誤信息
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)
}
}
當(dāng)點(diǎn)擊登錄按鈕箩绍,調(diào)用 ViewModel 中的 onLoginClicked()
進(jìn)行操作孔庭。
private fun login() {
viewModel.onLoginClicked(username.text.toString(), password.text.toString())
}
然后,在 Activity
中的 onDestroy()
調(diào)用 ViewModel 的 onDestroy()
釋放資源(這樣就可以分離觀察者)材蛛。
override fun onDestroy() {
viewModel.onDestroy()
super.onDestroy()
}
使用 Architecture Components 修改代碼
通過(guò)之前自己實(shí)現(xiàn) MVVM 的 ViewModel圆到,以便您可以輕松的看到差異。到目前為止卑吭,與 MVP 相比芽淡,MVVM 并沒(méi)有帶來(lái)更多的好處。
但也要一些不同豆赏,最重要的一點(diǎn)是您可以忘記 Activity 的銷(xiāo)毀挣菲,所以您可以脫離它的生命周期,隨時(shí)做你的工作掷邦。特別感謝 ViewModel 和 LiveData白胀。當(dāng) Activity
重新創(chuàng)建或者被銷(xiāo)毀時(shí),您無(wú)需擔(dān)心應(yīng)用的崩潰抚岗。
這是工作原理:當(dāng) Activity
被重新創(chuàng)建或杠,ViewModel 仍然存在,當(dāng) Activity
被永久殺死的時(shí)候苟跪,將會(huì)調(diào)用 ViewModel 的 onCleared()
由于 LiveData 也具有生命周期意識(shí)廷痘,因此它知道何時(shí)跟 LifecycleOwner
建立和斷開(kāi)聯(lián)系。所以您無(wú)需關(guān)心它件已。
我并不打算深入講解 Architecture Components 的工作原理(因?yàn)樵诠俜降拈_(kāi)發(fā)者指南中有更深刻的解釋?zhuān)┧穸睿宰屛覀兝^續(xù)探索實(shí)現(xiàn) MVVM。
在項(xiàng)目中使用 Architecture Components篷扩,需要添加以下依賴(lài)
implementation "android.arch.lifecycle:extensions:1.1.1"
如果您使用其他組件兄猩,如:Room 〖矗或者在 AndroidX 上使用這些組件枢冤,更多內(nèi)容請(qǐng)參考 這里。
Architecture Components ViewModel
使用 ViewModel 非常簡(jiǎn)單铜秆,你只需要繼承 ViewModel 即可淹真。
class LoginViewModel(private val loginInteractor: LoginInteractor) : ViewModel()
刪除 onDestroy()
,因?yàn)樗辉傩枰肆搿N覀兛梢詫⑨尫刨Y源的代碼核蘸,轉(zhuǎn)移到 onCleared()
巍糯,這樣我們就不需要在 Activity
的 onCreate()
中添加觀察,onDestroy()
中移除觀察客扎。就和我們無(wú)需關(guān)心 onCleared()
的調(diào)用時(shí)機(jī)一樣祟峦。
override fun onCleared() {
stateObservable.clearObservers()
super.onCleared()
}
現(xiàn)在,讓我們回到 LoginActivity
中徙鱼,創(chuàng)建一個(gè)具有延遲屬性的 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)
}
當(dāng) ViewModel 不需要通過(guò)構(gòu)造傳遞參數(shù)時(shí)袱吆,可以按照上述方法實(shí)現(xiàn)厌衙。但是當(dāng)我們需要 ViewModel 通過(guò)構(gòu)造傳遞參數(shù)時(shí),則必須聲明一個(gè)工廠類(lèi)绞绒。
class LoginViewModelFactory(private val loginInteractor: LoginInteractor) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
LoginViewModel(loginInteractor) as T
}
在 Activity
中通過(guò)以下方式獲取 ViewModel 實(shí)例
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 類(lèi)迅箩,需要注意的一點(diǎn)是,LiveData 默認(rèn)情況是不可變的(您無(wú)法改變其值)处铛。
這很棒,因?yàn)槲覀兿M枪驳墓战遥奖?Observer 可以訂閱撤蟆。但我們不希望在其他地方被修改。
但是堂污,另一方面家肯,數(shù)據(jù)需要是可變的,不然我們?yōu)槭裁磿?huì)觀察它呢盟猖?因此讨衣,訣竅是使用一個(gè)私有的屬性,并提供一個(gè)公共的 getter
式镐。
在 kotlin 中反镇,它將是一個(gè)私有的屬性,和一個(gè)公共的 get()
屬性娘汞。
private val _loginState: MutableLiveData<ScreenState<LoginState>> = MutableLiveData()
val loginState: LiveData<ScreenState<LoginState>>
get() = _loginState
而且我們也不再需要 onCleared()
了歹茶,因?yàn)?LiveData 具有生命周期意識(shí),它將在正確的時(shí)間停止觀察你弦。
要觀察它惊豺,最簡(jiǎn)潔的方式如下:
viewModel.loginState.observe(::getLifecycle, ::updateUI)
如果你不明白 函數(shù)引用,請(qǐng)查看我之前關(guān)于 函數(shù)引用 的文章禽作。
updateUI()
需要 ScreenState 作為參數(shù)尸昧,以便它適合 LiveData 的返回值。我可以將它用作函數(shù)引用旷偿。
private fun updateUI(screenState: ScreenState<LoginState>?) {
...
}
MainViewModel 也不需要 onResume()
了烹俗,相反爆侣,我們可以重寫(xiě)屬性的 getter
,并在 LiveData 第一次觀察時(shí)衷蜓,執(zhí)行請(qǐng)求累提。
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
的代碼和之前的類(lèi)似。
viewModel.mainState.observe(::getLifecycle, ::updateUI)
注意
之前的代碼似乎有點(diǎn)復(fù)雜磁浇,主要是因?yàn)槭褂昧诵碌目蚣苷悖?dāng)您了解它是如何工作的,一切將變得非常簡(jiǎn)單置吓。
肯定有一些新的樣板代碼无虚,例如 ViewModelFactory 和 獲取 ViewModel,或防止外部人員使用 LiveData 所定義的兩個(gè)屬性衍锚。我通過(guò)使用 Kotlin 的一些特性簡(jiǎn)化了本文的一些內(nèi)容友题,可以使您的代碼更加簡(jiǎn)潔,為了簡(jiǎn)單起見(jiàn)戴质,我并不打算在這里添加它們度宦。
正如我在開(kāi)頭所說(shuō)的,您是否使用 MVVM 或者 MVP 完全取決于您自己告匠。如果您目前的架構(gòu)使用 Mvp 運(yùn)行良好戈抄,我認(rèn)為沒(méi)有重構(gòu)的沖動(dòng),但了解 MVVM 的工作原理很有意思后专。因?yàn)槟t早會(huì)需要它划鸽。
我認(rèn)為我們?nèi)栽谔剿鳎?Android 中使用 MVVM 和架構(gòu)組件最優(yōu)的解決方案戚哎,我相信我的方案并不完美裸诽。所以,請(qǐng)讓我聽(tīng)到您內(nèi)心不同的聲音型凳,我很樂(lè)意根據(jù)反饋更新文章丈冬。
您可以在 GitHub 查看完整的代碼示例,(請(qǐng) star 支持 )