ViewModels and LiveData- Patterns + AntiPatterns

9e473ad6603a4539a67c8f7baa105442.png

点击上方蓝字关注我,知识会给你力量

4bc37d93d5cf4270f38ac8b06f8d75c8.png

这个系列我做了协程和Flow开发者的一系列文章的翻译,旨在了解当前协程、Flow、LiveData这样设计的原因,从设计者的角度,发现他们的问题,以及如何解决这些问题,pls enjoy it。

Views and ViewModels

Distributing responsibilities

c86c6757ac8475a03b1786f2595692f1.png
img

理想情况下,ViewModels不应该知道关于Android的任何事情。这可以提高可测试性、泄漏安全性和模块化。一般的经验法则是,确保在你的ViewModels中没有android.*的导入(android.arch.*等例外)。这同样适用于presenters。

  • ❌ 不要让ViewModels(和Presenters)知道Android框架类的情况

条件语句、循环和一般决策应该在ViewModels或应用程序的其他层中完成,而不是在Activities或Fragments中。视图通常没有单元测试(除非你使用Robolectric),所以代码行数越少越好。视图应该只知道如何显示数据并将用户事件发送到ViewModel(或Presenter)。这就是所谓的被动视图模式。

  • ✅将Activity和Fragment中的逻辑保持在最低限度

View references in ViewModels

视图模型与Activity或Fragment有不同的作用域。当一个ViewModel活着并运行时,一个Activity可以处于其生命周期的任何状态。在ViewModel不知道的情况下,Activity和Fragment可以被销毁并再次创建。

5d822cf5132fdfeb41bf66487d5d7554.png
img

将视图(Activity或Fragment)的引用传递给ViewModel是一个严重的风险。让我们假设ViewModel从网络上请求数据,并且数据在一段时间后回来。这时,View的引用可能会被破坏,也可能是一个不再可见的旧Activity,产生内存泄漏,并可能导致崩溃。

  • ❌ 避免在ViewModels中对View进行引用。

在ViewModels和View之间进行通信的推荐方式是观察者模式,使用LiveData或来自其他库的观察变量方式。

Observer Pattern

b06f5ce0035190a65b7436f255cfc6c2.png
img

在Android中设计表现层的一个非常方便的方法是让View(Activity或Fragment)观察(订阅)ViewModel的变化。由于ViewModel并不了解Android,所以它不知道Android是如何喜欢频繁地杀死View的。这有一些好处。

  • ViewModel在配置变化时被持久化,所以当重新请求发生时,不需要重新查询外部数据源(如数据库或网络)。

  • 当长期运行的操作结束时,ViewModel中的观察变量会被更新。数据是否被观察并不重要。当试图更新不存在的视图时,不会发生空指针异常。

  • ViewModels不引用视图,所以内存泄漏的风险较小。

private void subscribeToModel() {
  // Observe product data
  viewModel.getObservableProduct().observe(this, new Observer<Product>() {
      @Override
      public void onChanged(@Nullable Product product) {
        mTitle.setText(product.title);
      }
  });
}
  • ✅不要把数据推送给UI,而是让UI观察到它的变化。

Fat ViewModels

只要能让你分离关注点,就是一个好主意。如果你的ViewModel容纳了太多的代码或者有太多的责任,可以考虑。

  • 将一些逻辑转移到与ViewModel相同范围的presenter中。它将与你的应用程序的其他部分通信,并更新ViewModel中的LiveData持有者。

  • 添加一个Domain layer并采用Clean Architecture。这将导致一个非常可测试和可维护的架构。它也有利于快速离开主线程。在Architecture Blueprints中有一个Clean Architecture的例子。

例子在这里:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

  • ✅ 分散责任,如果需要的话,添加领域层。

Using a data repository

正如在《应用程序架构指南》中看到的那样,大多数应用程序都有多个数据源,例如。

  • 远程:网络或云

  • 本地:数据库或文件

  • 内存中的缓存

在你的应用程序中设置一个数据层是个好主意,完全不知道你的表现层。让缓存和数据库与网络保持同步的算法并非易事。建议有一个单独的存储库类作为处理这种复杂性的单一入口。

如果你有多个非常不同的数据模型,可以考虑添加多个存储库。

  • ✅ 添加一个数据存储库作为你的数据的单点入口

Dealing with data state

考虑这个场景:你正在观察一个由ViewModel暴露的LiveData,它包含一个要显示的项目列表。视图如何区分正在加载的数据、网络错误和一个空列表?

你可以从ViewModel中暴露出一个LiveData。例如,MyDataState可以包含关于数据是否正在加载、是否已经成功加载或失败的信息。

150e1a2c31f66c62359c664fb4c04b02.png
img

你可以把数据包装在一个有状态和其他元数据(如错误信息)的类中。参见我们样本中的资源类:https://developer.android.com/jetpack/guide#addendum。

  • ✅使用包装器或另一个LiveData暴露你的数据的状态信息。

Saving activity state

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

Events

事件是发生一次的事情。ViewModels暴露了数据,但事件呢?例如,导航事件或显示Snackbar信息是只应执行一次的动作。

事件的概念与LiveData存储和恢复数据的方式并不完全相符。考虑一个有以下字段的ViewModel。

LiveData<String> snackbarMessage = new MutableLiveData<>();

一个Activity开始观察这个,ViewModel完成了一个操作,所以它需要更新消息。

snackbarMessage.setValue("Item saved!");

该Activity接收该值并显示Snackbar。这显然是有效的。

然而,如果用户旋转手机,新的Activity被创建并开始观察。当LiveData观察开始时,该Activity立即收到旧的值,这导致消息再次显示出来。

与其试图用库或架构组件的扩展来解决这个问题,不如将其作为一个设计问题来面对。我们建议你把你的事件作为你的状态的一部分。

  • ✅将事件设计成你的状态的一部分。更多细节请阅读LiveData与SnackBar、Navigation和其他事件(SingleLiveEvent案例)。

Leaking ViewModels

反应式范式在Android中运行良好,因为它允许在UI和你的应用程序的其他层之间建立一个方便的连接。LiveData是这个结构的关键组件,所以通常你的Activity和Fragment会观察LiveData实例。

ViewModels如何与其他组件通信由你决定,但要注意泄漏和边缘情况。考虑一下这个图,视图层使用观察者模式,数据层使用回调。

bb37993255108949672b9cc1bb9756d1.png
img

如果用户退出了应用程序,视图就会消失,所以ViewModel就不会再被观察。如果repository是一个单例或其他范围的应用程序,repository将不会被销毁,直到进程被杀死。这只会在系统需要资源或用户手动杀死应用程序时发生。如果repository持有对ViewModel中回调的引用,ViewModel将被暂时泄露。

95a65f20974ca4965ec6078e8e097487.png
img

如果ViewModel是轻量级的,或者操作被保证快速完成,这种泄漏就不是什么大问题。然而,情况并不总是这样的。理想情况下,只要没有任何视图在观察它们,ViewModel就应该是自由的。

daeeb4239ed7bc8d6f5a751ab1e4ebc9.png
img

你有很多选择来实现这一点。

  • 通过ViewModel.onCleared()你可以告诉repository放弃对ViewModel的回调。

  • 在repository中,你可以使用WeakReference,也可以使用事件总线(两者都容易被滥用,甚至被认为是有害的)。

  • 使用LiveData在存储库和ViewModel之间进行通信,其方式类似于在View和ViewModel之间使用LiveData。

这点用Flow也可以解决。

  • ✅考虑边缘情况、泄漏以及长期运行的操作会如何影响你架构中的实例。

  • ❌ 不要在ViewModel中放置对保存清洁状态或与数据有关的逻辑。你从ViewModel进行的任何调用都可能是最后一次。

LiveData in repositories

为了避免泄露ViewModels和回调地狱,可以像这样观察存储库。

8c176242be266a90dbc997ad24881300.png
img

当ViewModel被清除或视图的生命周期结束时,订阅被清除。

9f8c734b11737242ef5a619ad7cc09a3.png
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可能就是解决方案。

Extending LiveData

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
    }
}

When not to extend LiveData

你也可以使用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

  • ❌ 你通常不会扩展LiveData。让你的Activity或Fragment告诉ViewModel何时开始加载数据。

原文链接:https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54

向大家推荐下我的网站 https://xuyisheng.top/  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

往期推荐

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下👇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值