这个系列我做了协程和Flow开发者的一系列文章的翻译,旨在了解当前协程、Flow、LiveData这样设计的原因,从设计者的角度,发现他们的问题,以及如何解决这些问题,pls enjoy it。
img
理想情况下,ViewModels不应该知道关于Android的任何事情。这可以提高可测试性、泄漏安全性和模块化。一般的经验法则是,确保在你的ViewModels中没有android.*的导入(android.arch.*等例外)。这同样适用于presenters。
条件语句、循环和一般决策应该在ViewModels或应用程序的其他层中完成,而不是在Activities或Fragments中。视图通常没有单元测试(除非你使用Robolectric),所以代码行数越少越好。视图应该只知道如何显示数据并将用户事件发送到ViewModel(或Presenter)。这就是所谓的被动视图模式。
视图模型与Activity或Fragment有不同的作用域。当一个ViewModel活着并运行时,一个Activity可以处于其生命周期的任何状态。在ViewModel不知道的情况下,Activity和Fragment可以被销毁并再次创建。
img
将视图(Activity或Fragment)的引用传递给ViewModel是一个严重的风险。让我们假设ViewModel从网络上请求数据,并且数据在一段时间后回来。这时,View的引用可能会被破坏,也可能是一个不再可见的旧Activity,产生内存泄漏,并可能导致崩溃。
在ViewModels和View之间进行通信的推荐方式是观察者模式,使用LiveData或来自其他库的观察变量方式。
img
在Android中设计表现层的一个非常方便的方法是让View(Activity或Fragment)观察(订阅)ViewModel的变化。由于ViewModel并不了解Android,所以它不知道Android是如何喜欢频繁地杀死View的。这有一些好处。
private void subscribeToModel() {
// Observe product data
viewModel.getObservableProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
mTitle.setText(product.title);
}
});
}
只要能让你分离关注点,就是一个好主意。如果你的ViewModel容纳了太多的代码或者有太多的责任,可以考虑。
例子在这里:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
正如在《应用程序架构指南》中看到的那样,大多数应用程序都有多个数据源,例如。
在你的应用程序中设置一个数据层是个好主意,完全不知道你的表现层。让缓存和数据库与网络保持同步的算法并非易事。建议有一个单独的存储库类作为处理这种复杂性的单一入口。
如果你有多个非常不同的数据模型,可以考虑添加多个存储库。
考虑这个场景:你正在观察一个由ViewModel暴露的LiveData,它包含一个要显示的项目列表。视图如何区分正在加载的数据、网络错误和一个空列表?
你可以从ViewModel中暴露出一个LiveData。例如,MyDataState可以包含关于数据是否正在加载、是否已经成功加载或失败的信息。
img
你可以把数据包装在一个有状态和其他元数据(如错误信息)的类中。参见我们样本中的资源类:https://developer.android.com/jetpack/guide#addendum。
Activity状态是你在一个Activity消失时重新创建屏幕所需要的信息,这意味着该Activity被破坏或进程被杀死。旋转是最常见的情况,我们已经用ViewModels覆盖了这种情况。所以,状态被保存在ViewModel中是安全的。
然而,你可能需要在ViewModels也消失的其他情况下恢复状态:例如,当操作系统资源不足并杀死了你的进程时。
为了有效地保存和恢复UI状态,可以使用持久性、onSaveInstanceState()和ViewModels的组合。
对于一个例子,请看。ViewModels: 持久性、onSaveInstanceState()、恢复UI状态和加载器
https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090
事件是发生一次的事情。ViewModels暴露了数据,但事件呢?例如,导航事件或显示Snackbar信息是只应执行一次的动作。
事件的概念与LiveData存储和恢复数据的方式并不完全相符。考虑一个有以下字段的ViewModel。
LiveData<String> snackbarMessage = new MutableLiveData<>();
一个Activity开始观察这个,ViewModel完成了一个操作,所以它需要更新消息。
snackbarMessage.setValue("Item saved!");
该Activity接收该值并显示Snackbar。这显然是有效的。
然而,如果用户旋转手机,新的Activity被创建并开始观察。当LiveData观察开始时,该Activity立即收到旧的值,这导致消息再次显示出来。
与其试图用库或架构组件的扩展来解决这个问题,不如将其作为一个设计问题来面对。我们建议你把你的事件作为你的状态的一部分。
反应式范式在Android中运行良好,因为它允许在UI和你的应用程序的其他层之间建立一个方便的连接。LiveData是这个结构的关键组件,所以通常你的Activity和Fragment会观察LiveData实例。
ViewModels如何与其他组件通信由你决定,但要注意泄漏和边缘情况。考虑一下这个图,视图层使用观察者模式,数据层使用回调。
img
如果用户退出了应用程序,视图就会消失,所以ViewModel就不会再被观察。如果repository是一个单例或其他范围的应用程序,repository将不会被销毁,直到进程被杀死。这只会在系统需要资源或用户手动杀死应用程序时发生。如果repository持有对ViewModel中回调的引用,ViewModel将被暂时泄露。
img
如果ViewModel是轻量级的,或者操作被保证快速完成,这种泄漏就不是什么大问题。然而,情况并不总是这样的。理想情况下,只要没有任何视图在观察它们,ViewModel就应该是自由的。
img
你有很多选择来实现这一点。
这点用Flow也可以解决。
为了避免泄露ViewModels和回调地狱,可以像这样观察存储库。
img
当ViewModel被清除或视图的生命周期结束时,订阅被清除。
img
如果你尝试这种方法,会有一个问题:如果你不能访问LifecycleOwner,你如何从ViewModel订阅Repository?使用Transformations是解决这个问题的一个非常方便的方法。Transformations.switchMap让你创建一个新的LiveData,对其他LiveData实例的变化做出反应。它还允许在整个链条上携带观察者的生命周期信息。
LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> {
if (repoId.isEmpty()) {
return AbsentLiveData.create();
}
return repository.loadRepo(repoId);
}
);
在这个例子中,当触发器得到更新时,该函数被应用,结果被派发到下游。一个Activity将观察repo,同样的LifecycleOwner将被用于repository.loadRepo(id)调用。
只要你认为你在ViewModel中需要一个Lifecycle对象,一个Transformation可能就是解决方案。
LiveData最常见的用例是在ViewModels中使用MutableLiveData,并将它们作为LiveData公开,使它们从观察者那里不可改变。
如果你需要更多的功能,扩展LiveData会让你知道什么时候有活跃的观察者。例如,当你想开始监听一个位置或传感器服务时,这很有用。
public class MyLiveData extends LiveData<MyData> {
public MyLiveData(Context context) {
// Initialize service
}
@Override
protected void onActive() {
// Start listening
}
@Override
protected void onInactive() {
// Stop listening
}
}
你也可以使用onActive()来启动一些加载数据的服务,但除非你有很好的理由,否则你不需要等待LiveData的观察。一些常见的模式。给ViewModel添加一个start()方法,并尽快调用它: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
设置一个启动加载的属性:https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/ui/repo/RepoFragment.kt
原文链接:https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54
向大家推荐下我的网站 https://xuyisheng.top/ 点击原文一键直达
专注 Android-Kotlin-Flutter