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

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

Views and ViewModels

Distributing responsibilities

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可以被销毁并再次创建。

img

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

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

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

Observer Pattern

img

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

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

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

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

private void subscribeToModel() {// Observe product dataviewModel.getObservableProduct().observe(this, new Observer<Product>() {@Overridepublic 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可以包含关于数据是否正在加载、是否已经成功加载或失败的信息。

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

img

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

img

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

img

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

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

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

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

这点用Flow也可以解决。

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

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

LiveData in repositories

为了避免泄露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可能就是解决方案。

Extending LiveData

LiveData最常见的用例是在ViewModels中使用MutableLiveData,并将它们作为LiveData公开,使它们从观察者那里不可改变。

如果你需要更多的功能,扩展LiveData会让你知道什么时候有活跃的观察者。例如,当你想开始监听一个位置或传感器服务时,这很有用。

public class MyLiveData extends LiveData<MyData> {public MyLiveData(Context context) {// Initialize service}@Overrideprotected void onActive() {// Start listening}@Overrideprotected 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 欢迎大家访问

往期推荐

  • 起点客户端精准化测试的演进之路

  • 从精准化测试看ASM在Android中的强势插入-读懂diff

  • 带你了解LiveData重放污染的前世今生

  • Kotlin鱿鱼游戏大奖赛

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

< END >

作者:徐宜生

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

ViewModels and LiveData- Patterns + AntiPatterns相关推荐

  1. 【译】如何在 Android 中使用 Retrofit, Moshi, Coroutines Recycler View

    翻译说明: 原标题: How-To: Retrofit, Moshi, Coroutines & Recycler View for REST Web Service Operations w ...

  2. Android LiveData

    In this tutorial, we'll be discussing the LiveData architectural component in our Android Applicatio ...

  3. [转] Leaving patterns practices

    [J.D. Meier's Blog]"Life is like skiing.  Just like skiing, the goal is not to get to the botto ...

  4. Seven Microservices Anti-patterns

    What it Was, Was Microservices Buzzwords often give context to concepts that evolved and needed a go ...

  5. LiveData ViewModel 使用详解

    什么是 LiveData LiveData 是一个可观测的数据持有类,但是不同于通常的被观察者,LiveData 具有生命周期感知能力.通俗点说,LiveData 就是具有 "Live&qu ...

  6. 把 LiveData 用于事件传递那些坑

    0.前言 如果不是很了解 LiveData 和 Lifecycle 的同学可以先看一下我之前的文章 基于 Android Architecture Components 的 MVVM 浅析.同时安利下 ...

  7. livedata mvvm_Android MVVM LiveData数据绑定

    livedata mvvm We've already implemented MVVM using Data Binding and covered LiveData and Data Bindin ...

  8. Jetpack LiveData

    文章目录 Jetpack LiveData 概述 LiveData优点 基本使用 活跃状态与非活跃状态 LiveData转换 Transformations#map() Transformations ...

  9. LiveData 源码解析(2.4.1 版本)

    文章目录 1.LiveData 简介 2.LiveData 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 2.2.1 LiveData 简单使用 2.2.2 LiveData 扩展 2.2 ...

最新文章

  1. Python中知识点笔记
  2. 《第一行代码》学习笔记24-持久化技术(3)
  3. C++ warning:’xxx‘ has no out-of-line virtual method definitions...
  4. 学计算机平面设计可以找什么工作,大学生学了平面设计之后能找什么样的工作...
  5. php 文章浏览量 缓存,WordPress缓存文章浏览量访问不自动增加怎么办?WordPress缓存导致文章阅读数点赞数不更新...
  6. [LeetCode] Trapping Rain Water II 题解
  7. Python基础之模块管理
  8. 什么是php递归算法_PHP递归算法(三)
  9. Oracle 后台进程初探
  10. 计算机要学什么知识,学电脑要先学什么 学电脑要学习什么知识
  11. R语言可视化散点图、ggrepel包的geom_text_repel函数避免数据点之间的标签互相重叠(设置segment.square为假以获得斜曲线,segment.inflect设置为真以引入拐点
  12. ubuntu中U盘硬盘格式化(NTFS,FAT12,FAT16,FAT32,EXT4,EXT3,EXT2)
  13. Linux shell 更改为zsh一直shell not changed
  14. MongoDB:常见的面试题和答案
  15. ASCII 碼: 转义字符,正则表达式,特殊字符,模式匹配
  16. 【百度快照优化公司】网站的百度快照优化技巧
  17. 常见文件密码破解方法大放送
  18. 计算机毕业设计Node.js+Express智慧工地管理系统(源码+程序+lw+远程调试)
  19. 独家下载,阿里云云原生携 10+ 技术专家带来《云原生与云未来的新可能》
  20. anydesk linux安装_在Ubuntu 20.04上安装AnyDesk

热门文章

  1. OMRON CP系列PLC非致命错误009B
  2. Scala 传名参数、注解
  3. 公用网络dns服务器未响应,怎样解决公用网络中“DNS服务器未响应”的问题
  4. TPGS-cisplatin顺铂修饰维生素E聚乙二醇1000琥珀酸酯
  5. 2019年春季面试题
  6. 【模板/经典题型】闵可夫斯基和
  7. 【comsol学习3】-基本操作之同时打开多个文件
  8. 计算机网络名词解释和简答
  9. 《unix环境高级编程》--- 信号
  10. Android Activity底层启动过程分析