ViewModels and LiveData- Patterns + AntiPatterns
點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量
這個(gè)系列我做了協(xié)程和Flow開(kāi)發(fā)者的一系列文章的翻譯,旨在了解當(dāng)前協(xié)程、Flow、LiveData這樣設(shè)計(jì)的原因,從設(shè)計(jì)者的角度,發(fā)現(xiàn)他們的問(wèn)題,以及如何解決這些問(wèn)題,pls enjoy it。
Views and ViewModels
Distributing responsibilities
理想情況下,ViewModels不應(yīng)該知道關(guān)于Android的任何事情。這可以提高可測(cè)試性、泄漏安全性和模塊化。一般的經(jīng)驗(yàn)法則是,確保在你的ViewModels中沒(méi)有android.*的導(dǎo)入(android.arch.*等例外)。這同樣適用于presenters。
-
? 不要讓ViewModels(和Presenters)知道Android框架類(lèi)的情況
條件語(yǔ)句、循環(huán)和一般決策應(yīng)該在ViewModels或應(yīng)用程序的其他層中完成,而不是在Activities或Fragments中。視圖通常沒(méi)有單元測(cè)試(除非你使用Robolectric),所以代碼行數(shù)越少越好。視圖應(yīng)該只知道如何顯示數(shù)據(jù)并將用戶(hù)事件發(fā)送到ViewModel(或Presenter)。這就是所謂的被動(dòng)視圖模式。
-
?將Activity和Fragment中的邏輯保持在最低限度
View references in ViewModels
視圖模型與Activity或Fragment有不同的作用域。當(dāng)一個(gè)ViewModel活著并運(yùn)行時(shí),一個(gè)Activity可以處于其生命周期的任何狀態(tài)。在ViewModel不知道的情況下,Activity和Fragment可以被銷(xiāo)毀并再次創(chuàng)建。
將視圖(Activity或Fragment)的引用傳遞給ViewModel是一個(gè)嚴(yán)重的風(fēng)險(xiǎn)。讓我們假設(shè)ViewModel從網(wǎng)絡(luò)上請(qǐng)求數(shù)據(jù),并且數(shù)據(jù)在一段時(shí)間后回來(lái)。這時(shí),View的引用可能會(huì)被破壞,也可能是一個(gè)不再可見(jiàn)的舊Activity,產(chǎn)生內(nèi)存泄漏,并可能導(dǎo)致崩潰。
-
? 避免在ViewModels中對(duì)View進(jìn)行引用。
在ViewModels和View之間進(jìn)行通信的推薦方式是觀察者模式,使用LiveData或來(lái)自其他庫(kù)的觀察變量方式。
Observer Pattern
在Android中設(shè)計(jì)表現(xiàn)層的一個(gè)非常方便的方法是讓View(Activity或Fragment)觀察(訂閱)ViewModel的變化。由于ViewModel并不了解Android,所以它不知道Android是如何喜歡頻繁地殺死View的。這有一些好處。
-
ViewModel在配置變化時(shí)被持久化,所以當(dāng)重新請(qǐng)求發(fā)生時(shí),不需要重新查詢(xún)外部數(shù)據(jù)源(如數(shù)據(jù)庫(kù)或網(wǎng)絡(luò))。 -
當(dāng)長(zhǎng)期運(yùn)行的操作結(jié)束時(shí),ViewModel中的觀察變量會(huì)被更新。數(shù)據(jù)是否被觀察并不重要。當(dāng)試圖更新不存在的視圖時(shí),不會(huì)發(fā)生空指針異常。 -
ViewModels不引用視圖,所以?xún)?nèi)存泄漏的風(fēng)險(xiǎn)較小。
private void subscribeToModel() {
// Observe product data
viewModel.getObservableProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
mTitle.setText(product.title);
}
});
}
-
?不要把數(shù)據(jù)推送給UI,而是讓UI觀察到它的變化。
Fat ViewModels
只要能讓你分離關(guān)注點(diǎn),就是一個(gè)好主意。如果你的ViewModel容納了太多的代碼或者有太多的責(zé)任,可以考慮。
-
將一些邏輯轉(zhuǎn)移到與ViewModel相同范圍的presenter中。它將與你的應(yīng)用程序的其他部分通信,并更新ViewModel中的LiveData持有者。 -
添加一個(gè)Domain layer并采用Clean Architecture。這將導(dǎo)致一個(gè)非常可測(cè)試和可維護(hù)的架構(gòu)。它也有利于快速離開(kāi)主線(xiàn)程。在Architecture Blueprints中有一個(gè)Clean Architecture的例子。
例子在這里:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
-
? 分散責(zé)任,如果需要的話(huà),添加領(lǐng)域?qū)印?
Using a data repository
正如在《應(yīng)用程序架構(gòu)指南》中看到的那樣,大多數(shù)應(yīng)用程序都有多個(gè)數(shù)據(jù)源,例如。
-
遠(yuǎn)程:網(wǎng)絡(luò)或云 -
本地:數(shù)據(jù)庫(kù)或文件 -
內(nèi)存中的緩存
在你的應(yīng)用程序中設(shè)置一個(gè)數(shù)據(jù)層是個(gè)好主意,完全不知道你的表現(xiàn)層。讓緩存和數(shù)據(jù)庫(kù)與網(wǎng)絡(luò)保持同步的算法并非易事。建議有一個(gè)單獨(dú)的存儲(chǔ)庫(kù)類(lèi)作為處理這種復(fù)雜性的單一入口。
如果你有多個(gè)非常不同的數(shù)據(jù)模型,可以考慮添加多個(gè)存儲(chǔ)庫(kù)。
-
? 添加一個(gè)數(shù)據(jù)存儲(chǔ)庫(kù)作為你的數(shù)據(jù)的單點(diǎn)入口
Dealing with data state
考慮這個(gè)場(chǎng)景:你正在觀察一個(gè)由ViewModel暴露的LiveData,它包含一個(gè)要顯示的項(xiàng)目列表。視圖如何區(qū)分正在加載的數(shù)據(jù)、網(wǎng)絡(luò)錯(cuò)誤和一個(gè)空列表?
你可以從ViewModel中暴露出一個(gè)LiveData
你可以把數(shù)據(jù)包裝在一個(gè)有狀態(tài)和其他元數(shù)據(jù)(如錯(cuò)誤信息)的類(lèi)中。參見(jiàn)我們樣本中的資源類(lèi):https://developer.android.com/jetpack/guide#addendum。
-
?使用包裝器或另一個(gè)LiveData暴露你的數(shù)據(jù)的狀態(tài)信息。
Saving activity state
Activity狀態(tài)是你在一個(gè)Activity消失時(shí)重新創(chuàng)建屏幕所需要的信息,這意味著該Activity被破壞或進(jìn)程被殺死。旋轉(zhuǎn)是最常見(jiàn)的情況,我們已經(jīng)用ViewModels覆蓋了這種情況。所以,狀態(tài)被保存在ViewModel中是安全的。
然而,你可能需要在ViewModels也消失的其他情況下恢復(fù)狀態(tài):例如,當(dāng)操作系統(tǒng)資源不足并殺死了你的進(jìn)程時(shí)。
為了有效地保存和恢復(fù)UI狀態(tài),可以使用持久性、onSaveInstanceState()和ViewModels的組合。
對(duì)于一個(gè)例子,請(qǐng)看。ViewModels: 持久性、onSaveInstanceState()、恢復(fù)UI狀態(tài)和加載器
https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090
Events
事件是發(fā)生一次的事情。ViewModels暴露了數(shù)據(jù),但事件呢?例如,導(dǎo)航事件或顯示Snackbar信息是只應(yīng)執(zhí)行一次的動(dòng)作。
事件的概念與LiveData存儲(chǔ)和恢復(fù)數(shù)據(jù)的方式并不完全相符??紤]一個(gè)有以下字段的ViewModel。
LiveData<String> snackbarMessage = new MutableLiveData<>();
一個(gè)Activity開(kāi)始觀察這個(gè),ViewModel完成了一個(gè)操作,所以它需要更新消息。
snackbarMessage.setValue("Item saved!");
該Activity接收該值并顯示Snackbar。這顯然是有效的。
然而,如果用戶(hù)旋轉(zhuǎn)手機(jī),新的Activity被創(chuàng)建并開(kāi)始觀察。當(dāng)LiveData觀察開(kāi)始時(shí),該Activity立即收到舊的值,這導(dǎo)致消息再次顯示出來(lái)。
與其試圖用庫(kù)或架構(gòu)組件的擴(kuò)展來(lái)解決這個(gè)問(wèn)題,不如將其作為一個(gè)設(shè)計(jì)問(wèn)題來(lái)面對(duì)。我們建議你把你的事件作為你的狀態(tài)的一部分。
-
?將事件設(shè)計(jì)成你的狀態(tài)的一部分。更多細(xì)節(jié)請(qǐng)閱讀LiveData與SnackBar、Navigation和其他事件(SingleLiveEvent案例)。
Leaking ViewModels
反應(yīng)式范式在Android中運(yùn)行良好,因?yàn)樗试S在UI和你的應(yīng)用程序的其他層之間建立一個(gè)方便的連接。LiveData是這個(gè)結(jié)構(gòu)的關(guān)鍵組件,所以通常你的Activity和Fragment會(huì)觀察LiveData實(shí)例。
ViewModels如何與其他組件通信由你決定,但要注意泄漏和邊緣情況??紤]一下這個(gè)圖,視圖層使用觀察者模式,數(shù)據(jù)層使用回調(diào)。
如果用戶(hù)退出了應(yīng)用程序,視圖就會(huì)消失,所以ViewModel就不會(huì)再被觀察。如果repository是一個(gè)單例或其他范圍的應(yīng)用程序,repository將不會(huì)被銷(xiāo)毀,直到進(jìn)程被殺死。這只會(huì)在系統(tǒng)需要資源或用戶(hù)手動(dòng)殺死應(yīng)用程序時(shí)發(fā)生。如果repository持有對(duì)ViewModel中回調(diào)的引用,ViewModel將被暫時(shí)泄露。
如果ViewModel是輕量級(jí)的,或者操作被保證快速完成,這種泄漏就不是什么大問(wèn)題。然而,情況并不總是這樣的。理想情況下,只要沒(méi)有任何視圖在觀察它們,ViewModel就應(yīng)該是自由的。
你有很多選擇來(lái)實(shí)現(xiàn)這一點(diǎn)。
-
通過(guò)ViewModel.onCleared()你可以告訴repository放棄對(duì)ViewModel的回調(diào)。 -
在repository中,你可以使用WeakReference,也可以使用事件總線(xiàn)(兩者都容易被濫用,甚至被認(rèn)為是有害的)。 -
使用LiveData在存儲(chǔ)庫(kù)和ViewModel之間進(jìn)行通信,其方式類(lèi)似于在View和ViewModel之間使用LiveData。
這點(diǎn)用Flow也可以解決。
-
?考慮邊緣情況、泄漏以及長(zhǎng)期運(yùn)行的操作會(huì)如何影響你架構(gòu)中的實(shí)例。 -
? 不要在ViewModel中放置對(duì)保存清潔狀態(tài)或與數(shù)據(jù)有關(guān)的邏輯。你從ViewModel進(jìn)行的任何調(diào)用都可能是最后一次。
LiveData in repositories
為了避免泄露ViewModels和回調(diào)地獄,可以像這樣觀察存儲(chǔ)庫(kù)。
當(dāng)ViewModel被清除或視圖的生命周期結(jié)束時(shí),訂閱被清除。
如果你嘗試這種方法,會(huì)有一個(gè)問(wèn)題:如果你不能訪問(wèn)LifecycleOwner,你如何從ViewModel訂閱Repository?使用Transformations是解決這個(gè)問(wèn)題的一個(gè)非常方便的方法。Transformations.switchMap讓你創(chuàng)建一個(gè)新的LiveData,對(duì)其他LiveData實(shí)例的變化做出反應(yīng)。它還允許在整個(gè)鏈條上攜帶觀察者的生命周期信息。
LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> {
if (repoId.isEmpty()) {
return AbsentLiveData.create();
}
return repository.loadRepo(repoId);
}
);
在這個(gè)例子中,當(dāng)觸發(fā)器得到更新時(shí),該函數(shù)被應(yīng)用,結(jié)果被派發(fā)到下游。一個(gè)Activity將觀察repo,同樣的LifecycleOwner將被用于repository.loadRepo(id)調(diào)用。
只要你認(rèn)為你在ViewModel中需要一個(gè)Lifecycle對(duì)象,一個(gè)Transformation可能就是解決方案。
Extending LiveData
LiveData最常見(jiàn)的用例是在ViewModels中使用MutableLiveData,并將它們作為L(zhǎng)iveData公開(kāi),使它們從觀察者那里不可改變。
如果你需要更多的功能,擴(kuò)展LiveData會(huì)讓你知道什么時(shí)候有活躍的觀察者。例如,當(dāng)你想開(kāi)始監(jiān)聽(tīng)一個(gè)位置或傳感器服務(wù)時(shí),這很有用。
public class MyLiveData extends LiveData<MyData> {
public MyLiveData(Context context) {
// Initialize service
}
@Override
protected void onActive() {
// Start listening
}
@Override
protected void onInactive() {
// Stop listening
}
}
When not to extend LiveData
你也可以使用onActive()來(lái)啟動(dòng)一些加載數(shù)據(jù)的服務(wù),但除非你有很好的理由,否則你不需要等待LiveData的觀察。一些常見(jiàn)的模式。給ViewModel添加一個(gè)start()方法,并盡快調(diào)用它:https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/addedittask/AddEditTaskFragment.java#L64
設(shè)置一個(gè)啟動(dòng)加載的屬性:https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/ui/repo/RepoFragment.kt
-
? 你通常不會(huì)擴(kuò)展LiveData。讓你的Activity或Fragment告訴ViewModel何時(shí)開(kāi)始加載數(shù)據(jù)。
原文鏈接:https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專(zhuān)注 Android-Kotlin-Flutter 歡迎大家訪問(wèn)
往期推薦
更文不易,點(diǎn)個(gè)“三連”支持一下??
