ViewModels : 一个简单的例子
两年多以前,我正在进行Android for Beginners的工作;这个课程带领学生从零编程到他们的第一个Android应用程序。作为课程的一部分,学生们建立了一个非常简单的单屏应用,名为Court-Counter。
Court-Counter是一个非常直接的应用,它的按钮可以修改篮球得分。不过这个成品应用有一个bug;如果你旋转手机,你当前的分数会莫名其妙地消失。
发生了什么事?旋转设备是应用程序在其生命周期中可能经历的少数配置更改之一,包括键盘可用性和改变设备语言。所有这些配置变化都会导致 "活动 "被拆毁并重新创建。
这种行为允许我们做一些事情,例如在设备侧向旋转时使用一个横向的特定布局。不幸的是,对于新的(有时是不太新的)工程师来说,这可能是一个令人头痛的问题。
在2017年谷歌I/O大会上,Android框架团队推出了一套新的架构组件,其中一个组件正是处理这个旋转问题的。
ViewModel类被设计为以生命周期意识的方式持有和管理UI相关的数据。这使得数据能够在配置变化(如屏幕旋转)中幸存下来。
这篇文章是探索ViewModel内涵的系列文章中的第一篇。在这篇文章中,我将。
- 解释一下ViewModels满足的基本需求
- 通过改变法院-计数器的代码以使用ViewModel来解决旋转问题。
- 仔细看看ViewModel和UI组件的关联性
潜在的问题
根本的挑战在于Android 活动生命周期有很多状态,而且由于配置的变化,单个活动可能会多次在这些不同的状态中循环。
当一个活动正在经历所有这些状态时,你也可能有需要保留在内存中的瞬时UI数据。我将把瞬时UI数据定义为UI所需的数据。例子包括用户输入的数据、运行时产生的数据或从数据库加载的数据。这些数据可能是位图图像、RecyclerView所需的对象列表,或者在本例中,是一个篮球比分。
以前,你可能会在配置更改时使用onRetainNonConfigurationInstance
来保存这些数据,并在另一端将其解包。但是,如果你的数据不需要知道或管理活动所处的生命周期状态,那不是很好吗?与其在活动中拥有像scoreTeamA
这样的变量,并因此与活动生命周期的所有奇思妙想联系在一起,不如将这些数据存储在活动之外的其他地方,这样会怎么样?这就是ViewModel类的目的。
在下图中,你可以看到一个活动的生命周期,它经历了一次轮换,然后最终结束。ViewModel的生命周期显示在相关活动生命周期的旁边。请注意,ViewModel可以很容易地与Fragments和Activities一起使用,我将其称为UI控制器。本例的重点是活动。
ViewModel存在于你第一次请求ViewModel的时候(通常是在onCreate
活动中),直到活动结束并销毁。在一个活动的生命周期中,onCreate
可能会被调用几次,例如当应用程序被旋转时,但ViewModel会一直存在。
一个非常简单的例子
设置和使用ViewModel有三个步骤。
- 通过创建一个扩展ViewModel的类,将您的数据与您的UI控制器分离开来。
- 在你的ViewModel和你的UI控制器之间设置通信。
- 在你的UI控制器中使用你的ViewModel
第1步:创建一个ViewModel类
Note。要创建一个ViewModel,你首先需要添加正确的生命周期依赖关系。请看如何这里。
一般来说,你将为应用中的每个屏幕制作一个ViewModel类。这个ViewModel类将保存与屏幕相关的所有数据,并为存储的数据设置了获取器和设置器。这就把显示用户界面的代码(在你的Activities和Fragments中实现)与你的数据分开,后者现在住在ViewModel中。所以,让我们为Court-Counter的一个屏幕创建一个ViewModel类。
public class ScoreViewModel extends ViewModel {
// Tracks the score for Team A
public int scoreTeamA = 0;
// Tracks the score for Team B
public int scoreTeamB = 0;
}
为了简洁起见,我选择将数据作为公共成员存储在我的ScoreViewModel.java
中,但创建getters和setters以更好地封装数据是一个好主意。
第2步:关联UI控制器和ViewModel。
你的UI控制器(又称活动或片段)需要了解你的ViewModel。这是为了让你的UI控制器能够显示数据,并在UI交互发生时更新数据,例如在Court-Counter中按下按钮增加一个团队的分数。
但是,ViewModels不应该持有对Activities、Fragments或Contexts的引用。***此外,ViewModels不应该包含包含对UI控制器的引用的元素,如View,因为这将创建对Context的间接引用。
你不应该存储这些对象的原因是,ViewModels会超过你的特定UI控制器实例--如果你将一个Activity旋转三次,你刚刚创建了三个不同的Activity实例,但你只有一个ViewModel。
考虑到这一点,让我们创建这个UI控制器/ViewModel关联。你要在UI控制器中为你的ViewModel创建一个成员变量。然后在onCreate
中,你应该调用。
ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
在Court-Counter的案例中,这看起来像。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
// Other setup code below...
}
**注意。There’no contexts in ViewModels”规则有一个例外。有时你可能需要一个应用上下文(而不是活动上下文),以便与系统服务等事项一起使用。将应用上下文存储在ViewModel中是可以的,因为应用上下文与应用生命周期相关联。这与活动上下文不同,后者是与活动生命周期联系在一起的。事实上,如果你需要一个应用程序上下文,你应该扩展AndroidViewModel,这只是一个包含应用程序引用的ViewModel。
第3步:在你的UI控制器中使用ViewModel。
要访问或改变UI数据,你现在可以使用ViewModel中的数据。下面是一个新的onCreate
方法的例子,以及一个通过给A队增加一分来更新分数的方法。
// The finished onCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
displayForTeamA(mViewModel.scoreTeamA);
displayForTeamB(mViewModel.scoreTeamB);
}
// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
displayForTeamA(mViewModel.scoreTeamA);
}
专业提示:ViewModel还可与另一个架构组件--LiveData很好地配合,在本系列中我不会对其进行深入探讨。使用LiveData的额外好处是,它是可观察的:当数据发生变化时,它可以触发UI更新。你可以了解更多关于LiveData的信息这里。
仔细观察一下ViewModelsProviders.of
MainActivity第一次调用ViewModelProviders.of
方法时,会创建一个新的ViewModel实例。当这个方法再次被调用时,也就是onCreate
被调用时,它将返回与特定的Court-Counter MainActivity相关的已有的ViewModel。这就是保留数据的原因。
只有当你把正确的UI控制器作为第一个参数传入时,这才有效。虽然你应该绝不在ViewModel内存储一个UI控制器,但ViewModel类确实在幕后跟踪ViewModel和UI控制器实例之间的关联,使用你传递进来的UI控制器作为第一个参数。
ViewModelProviders.of(<THIS ARGUMENT>).get(ScoreViewModel.class);
这使您能够拥有一个应用程序,它可以打开同一活动或片段的许多不同实例,但具有不同的ViewModel信息。让我们想象一下,如果我们扩展我们的Court-Counter示例,使其具有多个篮球比赛的分数。这些比赛以列表的形式呈现,然后点击列表中的一场比赛,就会打开一个看起来像我们当前的MainActivity的屏幕,但我将其称为GameScoreActivity。
对于你打开的每一个不同的游戏屏幕,如果你将ViewModel和GameScoreActivity
在onCreate
中关联起来,它将创建一个不同的ViewModel实例。如果你旋转其中一个屏幕,与相同的ViewModel的连接会被保持。
所有这些逻辑都是通过调用ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
为你完成的。因此,只要你传入正确的UI控制器实例,它就能工作。
最后一个想法。ViewModels对于将你的UI控制器代码与填充你的UI的数据分离开来是非常有用的。尽管如此,它们并不是数据持久性和保存应用状态的万能药。在下一篇文章中,我将探讨活动生命周期与ViewModels的微妙互动,以及ViewModels与onSaveInstanceState
的比较。
结论和进一步的学习
在这篇文章中,我探讨了新的ViewModel类的基本原理。关键的收获是。
- ViewModel类旨在以具有生命周期意识的方式持有和管理UI相关的数据。这使得数据能够在配置变化(如屏幕旋转)中存活。
- ViewModels 将 UI 的实现与您的应用程序的数据分开。
- 一般来说,如果您的应用程序中的某个屏幕有瞬时数据,您应该为该屏幕的数据创建一个单独的ViewModel。
- ViewModel的生命周期从相关的UI控制器首次创建时开始,直到它被完全销毁为止。
- 永远不要在ViewModel中直接或间接存储UI控制器或Context。这包括将视图存储在ViewModel中。对UI控制器的直接或间接引用违背了将UI与数据分离的目的,并可能导致内存泄漏。
- ViewModel对象通常会存储LiveData对象,您可以从这里了解到更多信息。
- ViewModelProviders.of方法通过作为参数传入的UI控制器来跟踪ViewModel与哪些UI控制器相关联。
想了解更多关于ViewModel的信息吗?请看。
- 添加gradle依赖项的说明
- ViewModel 文档
- 使用Room with a View和Lifecycles Codelab指导ViewModel的练习
架构组件是根据你的反馈创建的。如果你对ViewModel或任何架构组件有问题或意见,请查看我们的反馈页面。关于这个系列的问题或建议?请留下评论!
我在几个月前开发的一个App上做了一些实验,添加了ViewModel并通过LiveData组件连接到预先存在的DbFlow数据库。我非常喜欢所得到的结果!
我不得不说,谷歌在这些架构组件上做了很好的工作:易于理解、易于使用和易于整合。
继续专注于架构,你正在一个伟大的操作系统上建立一个伟大的框架;)
很高兴听到这个消息;我会让团队知道的 :)
Lyla Fujiwara,在一个使用 REST api 的应用程序中,假设在 MySQL 数据库中更新了一个用户的关注计数。即使没有使用服务来检索更新计数,livedata是否会自动更新该视图?
如果您使用Room + LiveData,您很可能会得到您所寻找的结果。
LiveData是可观察的,这意味着你的UI控制器(活动/片段)可以观察LiveData的变化。如果你使用Room来生成你的数据库,你可以获得与你的数据库相关的LiveData。然后当你的数据库更新时,你的LiveData会自动让观察者知道你的数据库已经更新。
在这种情况下,你将不需要使用服务来检索更新的计数,相反,当数据更新时,你的用户界面将被自动触发。
你仍然需要编写代码来设置观察者关系,并让UI控制器用新数据更新你的视图。在Room codelab中可以找到这个代码的一个很好的例子(我将链接到codelab的第3步,其中显示了Room+LiveData)。
很好的文章,谢谢Lyla Fujiwara :)
关于ViewModels,有一点值得一提,但似乎还没有被记录。这让我头疼了好几个小时,并通过源代码来弄清楚。
ViewModels使用Fragments来保留其数据。如果像我一样,你希望FragmentManager中只有你的片段,所以不考虑将它们全部清除,那么请注意,你也在清除之前创建的ViewModels。
永远不要清除任何具有 "android.arch.lifecycle.HolderFragment "实例的片段。
嘿,Mark — 希望晚点回复总比没有好。一般来说,清除所有片段是一种反模式,因为有些组件(ViewModel是其中之一)会代表你在后台使用片段。GoogleApiClient’的enableAutoManage()是另一个使用片段的库,它也不会对片段清除做出良好的反应。
嗨,您好。
我对onSaveInstanceState
的事情感到好奇。我正在研究使用ViewModel
的主要原因是,目前我只是保存了用户正在查看的任何内容的一个键,然后在配置发生变化时重新加载它。这并不坏,因为我使用的是启用了持久性的Firebase RTDB(所以数据被缓存),但那额外的十几秒的等待时间仍然很糟糕。无论如何,我看了一下文档,似乎没有办法弄清楚ViewModel
是来自缓存还是反射性地创建。我需要onCreate
这样的东西来正确处理我的状态,如果我的进程死亡。
val hadModel = ViewModelProviders.of(this).has(MyViewModel::class.java)
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
if (!hadModel) {
if (savedInstanceState != null) {
model.init(savedInstanceState.getMyStuff())
} else {
model.init(arguments.getMyStuff())
}
}
useModel()
还是说LiveData
是以某种方式出现的?我想我也可以做一些事情,不管ViewModelProvider
是否有我的模型,我都会调用init
,如果它已经有我的键,就不执行该方法。基本上,我想获得关于我们应该如何处理onSaveInstanceState
的《意见指南》。😁
这些都是很好的问题,我一直在写一篇博文,以便为您提供一些意见:)
首先,我想确认这就是ViewModel的确切用途(对配置的变化进行缓存,所以你不需要从数据库中重新加载),所以做得很好。
I could also do something where I call init regardless of whether or not the the ViewModelProvider had my model and just no-op the method if it already has my keys.
这几乎是正确的,但有一些评论。ViewModel是你的UI数据所在的地方。如果ViewModel(而不是ViewModelProvider)没有它所需要的UI数据,例如在系统关闭后重新启动的活动中,它应该委托主线程从数据库中重新加载数据。我说委托重载数据是因为从技术上讲ViewModels是用来保存UI数据的,而不是用来处理数据缓存和加载逻辑的。如果你遵循 Guide to App Architecture,这将从 ViewModel 委托给一个 “repository” 类。你可以在该指南中阅读更多相关内容。
所以在Activity中检查你是否有一个带有你的key的SavedInstanceState包,如果你有的话就把key传给ViewModel,然后让ViewModel来处理它在那里的加载。如果该活动由于配置的改变而被重新启动,它应该看到它已经有了数据,而不做无用的重新加载。
LiveData是一个切入的概念,它解决了“当我的数据被加载时,我如何将其传回给正确的UI控制器?”的问题。它是一个持有数据的对象,当数据发生变化时,它会触发观察者。如果你遵循Guide to App Architecture,LiveData对象被保存在ViewModels中。一般来说,LiveData对象由UI控制器观察,以便UI控制器能够在变化时更新UI。
你可以把所有的数据放在LiveData里面,放在你的ViewModel里面。如果你使用一个键来查找用户,你也可以使用Transformations类的辅助方法,以便在你的用户键发生变化时轻松触发从数据库重新加载用户数据。来自文档。
private LiveData<User> getUser(String id) {
…;
}
LiveData<String> userId = …;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
非常喜欢这篇文章,等不及要看下一篇文章了 :)
嗨,您好。
我不禁注意到,你只是把ViewModel作为一个包含数据的DTO。它没有任何行为,只有状态。`displayForTeamA()`函数不应该是ViewModel而不是View的成员吗?从Android自己的文档(https://developer.android.com/topic/libraries/architecture/viewmodel#kotlin)来看,不应该是这样的吗?
mViewModel.displayForTeamA().observe(this, Observer { score ->
//更新用户界面
})
嗨,您好。
谢谢你提供这个有启发性的例子。我下载了它,并在安卓8.1.0版的小米A1设备和API 27版的虚拟Nexus 5X上试用。该应用程序在这两个设备上运行良好,但在旋转或返回/恢复操作中,它不能存活。
使用API 24(如你的原始代码)和API 27也有同样的现象。
知道什么地方会出错吗?
谢谢你。
萨伊人
你回答了所有人,你的解释比其他 "大师 "好1000倍,从简单的例子开始,并建立起来。很明显,你是一个好人。我不喜欢恭维名人,因为他们通常被低估了,但我不能不佩服你。
太神奇了 :) 谢谢你的努力。
嘿!伟大的帖子&ndash;祝贺Lyla Fujiwara !我认为这是很重要的。
我正在开发ADN项目。我希望执行HTTP请求并从JSON中接收ArrayList。在我的main_activity中,我正在加载与最受欢迎/最高评分电影有关的图像(用户正在从位于AppBar的旋转器中选择它)。当旋转屏幕时,Android再次执行请求。我希望能保存结果。我使用了Parceables + onSaveInstance,但它没有帮助。你能告诉我ViewModel在这个问题上是否合适吗?
谢谢 !"。
汪建华
ViewModel可以提供帮助,而且更好的是,你可能想简单地创建一个Repository类,负责加载和保存数据。
首先要指出的是--不要在你的Activity中进行网络或数据库请求,原因正是你所指出的--当你的生命周期中发生各种事件时(比如你旋转手机),网络请求就会不必要地再次进行。在这种特殊情况下,你可以创建一个ViewModel并将加载数据的代码移到那里。
在ViewModel类中,你究竟把这段网络代码放在哪里,取决于你想何时加载数据。如果你想在活动启动时加载它,你可以把它放在ViewModel’的构造器中。因为你’使用ViewModelProviders.of
和而不是再次构造ViewModel,所以ViewModel构造函数代码不会在旋转时被再次调用。
这将解决你所提到的问题。
但由于一些原因,这并不是最稳健的解决方案。
1.1.ViewModels只能在旋转中存活,如果你关闭你的活动并重新打开它,它将再次发出网络请求,即使它在几秒钟前就这样做了。解决方案:每当你加载数据时,将其保存在本地的数据库中。这不是一个简单的单行解决方案,它需要添加逻辑来检查你是否在数据库中保存了数据,并决定是否要从网络中重新加载数据。
2.你可能已经注意到,增加一个数据库和何时从数据库加载与网络加载的逻辑需要增加相当数量的代码。这可能导致ViewModel成为一个充满数据和网络逻辑的非常臃肿的类。解决方案:不要让它发生!创建一个存储库类,其目的是为了管理下载/保存数据。
非常感谢您
非常好的读物!
我做了一个类似于视图模型的替代解决方案,方法略有不同,更注重对数据到视图的绑定过程的完全控制。
如果能得到一些反馈,那就太棒了!https://sedstrom.github.io/Witch-Android/。
非常好。我将等待你的LiveData教程。我不太清楚这一点。
如果你在阅读完文档后还有疑问,我建议看一下Lifecycles codelab。它具有指导性和实用性。Step 3有一个使用LiveData和ViewModel的例子。
谢谢你的这篇文章。
女士。
我正在学习这个惊人的课程。以最简单的方式学习,有这么多乐趣。很感谢你。非常感谢你让我免费学习。^_^
21/3/22.惊人的和容易理解的!谢谢!请注意,'ViewModelProviders'已被弃用。请使用'ViewModelProvider'和'ViewModelStoreOwner'。下面是一个简单的例子。
mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
简单明了,谢谢你。
非常好的解释
有史以来最好的解释,我明白了从A到Z的所有内容,非常感谢。
1. 建立存储页面控制器数据的 ViewModel;
2. 建立页面控制器与 ViewModel 的联系;
3. 在页面控制器中使用 ViewModel。
界面所需的数据。这里包含界面显示所需的一切数据。
你必须在ViewModel的onCleared()方法上清除所关心的变量吗?
我不是一个Android程序员,但我正试图加深我对系统和UI架构的了解。因此,我的问题是从更多的理论角度出发的。
你谈到了 "ViewModel",但对你来说,它与 "Model "究竟有什么不同?
对我来说,"模型 "持有应用程序的当前状态,与它的逻辑相联系,与UI完全分离。另一方面,"ViewModel "持有一个特定于UI的状态,尽管没有与具体的 "View "对象(活动、UI组件等)相联系。
所以--对我来说--分数是一个 "模型 "的东西,它是核心程序逻辑所操作的数据。
在 "ViewModel "中,我可以保存UI的信息,比如说--我应该呈现阿拉伯数字还是罗马数字,或者数字应该是什么颜色。这些设置肯定也会在旋转中幸存下来(所以,也不是视图/活动的一部分),但程序的核心逻辑并不关心这些(所以不是模型)。
我按下后退按钮,活动被破坏,然后再次来到这个活动,新的视图模型实例被创建,但在配置变化时,活动也被破坏,然后它使用在活动的onCreate方法中创建的旧实例,这怎么知道活动是由于配置变化还是按后退按钮而被破坏的呢?
它很实用,但在ViewModel中没有android包不是一个经验法则吗?还是说这应该是一个例外?
谢谢你的帖子。这个帖子对理解ViewModel真的很有帮助。)
Ciao Lyla,我想我明白你所教的东西的重要性,但是,作为Android和Java编程的新手,我没有成功地让你的例子工作。我想我已经以正确的方式更新了build.gradle文件,但MainActivity的 "ViewModelProviders、displayForTeamA和B、scoreTeamA和B,当然还有mViewModel "仍然是红色的(在编译时没有被识别)。
你是否有完整的代码和一些建议?我真的很想试试,看看你是怎么解释的!
提前感谢您
克劳迪奥
在片段中关联ViewModel的代码:mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class)。
已被弃用。请使用:mViewModel = ViewModelProvider(this).get(ScoreViewModel::class.java)
经过3年的努力,我现在开始学习...
这是我读过的关于Android中的MVVM的最简单和最直接的文章。所有其他文章都使用了臃肿的例子,并涵盖了更广泛的主题(如LiveData),这使得它更难掌握基本概念。祝贺你!
嗨,Lyla Fujiwara,这很有帮助:)非常感谢!
这是一个非常好的教程。
谢谢
我在想,为什么没有人问起单元测试?绝对是每个人都在为写好单元测试和干净的架构而苦恼,但却没有一个好的例子来说明Android架构组件的使用与单元测试。我认为这应该是继创建实例之后的第二件最重要的事情:为ViewModel提供单元测试。
你好,请问下一篇文章的链接在哪里?
首先,感谢你的这篇文章,它很好。
但是,当我试图实施它时,它对我不起作用。这是我的情况。
我有一个活动,其中只包含一个片段。我的片段中有两个textView。我的片段也有一个ViewModel。
在我的活动的onCreate()中,我正在添加这样的片段。
val fragment = fragmentManager.findFragmentByTag(HomeFragment.CLASS_NAME)
fragmentManager.beginTransaction().apply {
if (fragment == null)
replace(
R.id.fragment_container,
HomeFragment(),
HomeFragment.CLASS_NAME
)
.addToBackStack(null)
.commit()
else
replace(R.id.fragment_container, fragment)
.commit()
}
但是当我旋转我的设备时,Activity将以一个新的实例重新创建,我的片段也将创建并重新添加到我的Activity中,所以我将有一个我的Activity和片段的新实例,并且文本视图将被重新设置。
我怎样才能解决这个问题呢?
谢谢你。
莱拉,你给我发一下源代码吧。
如此简单而又有效!谢谢你
感谢Lyla Fujiwara你真的以伟大的方式解释了。
再次感谢❤。
非常感谢Lyla的这个伟大的帖子。它对我帮助很大。只有一件事你忘记了,就是在build.gradle中添加这行代码
implementation 'android.arch.lifecycle:extensions:1.1.1'
错别字
为什么不在Github上发布一个样本?它甚至没有提到要放在gradle上的内容... :(
我有一篇关于在许多活动之间使用ViewModel作为Singleton的文章。
我在等待反馈和评论。
https://medium.com/@droidbobakr/using-viewmodel-between-many-activities-d39bfad8a7ed
谢谢
这篇文章对我理解视图模型有很大的帮助。谢谢你,女士:)
很好的帖子,很喜欢这种简单的方式。
这是一篇很好的文章,我很惊讶我在Medium上发现了这篇文章,我去留言支持这篇文章(而不是谷歌返回的其他文章),结果发现其他人也都这么做了。不过,这篇文章对一个概念的解释还是很简单的,有简单扎实可行的例子和定义。
我试图在设备内部存储中销毁一个活动之前在onStop方法中保存该活动的状态,这样当应用程序重新打开时我就可以在onCreate方法中使用该状态。我怎样才能以独立于应用程序的方式做到这一点?有些应用程序可能不使用ViewModel或SavedInstanceState,所以我不能依靠这些来保存实例状态到内部存储。
我希望你能提供一个概述,说明动作是如何进行的,例如按Activity的addOneForTeamA方法所推断的按钮。例如,ViewModel的例子似乎只初始化了它的两个变量。我猜想addOneForTeamA方法应该在ScoreViewModel中,因为它修改了它的变量,由按下按钮触发,但它却在Activity中。
我一直在自学安卓系统,所以可能在这里错过了一些非常明显的东西。
I’ve chosen to have the data stored as public members in my ScoreViewModel.java
for brevity, but creating getters and setters to better encapsulate the data is a good idea.
我认为在活动中改变ViewModel的数据是由于在这个例子中ViewModel的字段是公共的。如果addOneForTeamA(View v)方法用setter/getter替换了直接的dot访问器,那么ScoreViewModel类将是处理改变其数据的那个。
mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
→
mViewModel.setScoreTeamA(mViewModel.getScoreTeamA() + 1);
然而,如果你想问为什么该活动不直接调用类似于
mViewModel.addOneForTeamA(),这将被定义在ScoreViewModel类中,那么我认为这与ScoreViewModel没有对活动的引用有关。因此,addOneForTeamA()方法中的第二条语句—displayForTeamA(mViewModel.scoreTeamA)/—将无法运行,活动将不知道要刷新视图显示。
我希望这是有意义的!
Lyla Fujiwara,我很高兴阅读您的博客,而且我已经阅读了您的Android for Beginners课程,真的很有帮助。我需要帮助,在我的应用程序中,我想从restApi获取数据,并在应用程序加载时将其存储在本地的sqlite db中,然后使用数据(列表)来填充到旋转器元素中。我想用房间和实时数据来做这件事。我对它很陌生,所以请你指导我。谢谢
嘿嘿,好文章!祝贺你
我注意到一个小错误,这不是什么大问题,但最后一张图片中的第三个标题与活动的图片不对应,只是集中在文章内的所有内容上XD
来自哥伦比亚的问候!
莱拉,你好。
你能不能做一个非常简单的Java分页组件的教程? codelabs有一个,但它是用Kotlin写的。
非常感谢你。
这是我第一次读到一个清晰而又简单的教程。
我将阅读你所写的全部内容。
谢谢你
我应该在ViewModel中存储我的数据。我应该在哪里检索我的数据— 视图或视图模型?
我正在做一个使用ViewModel而不使用LiveData的小实验。我从ViewModel中发出了一个GET请求,但后来意识到我无法与我的View进行通信,让它知道操作已经完成(从而移除进度旋钮...)。
我是否可以从我的视图中发出请求,而只是使用ViewModel进行存储?
我试图在不使用LiveData或持久化的情况下做一个小的ViewModel说明性的例子。
在这幅图中,最后三句话是什么意思?
我们知道,视图模型持有对LiveData的引用。当一个活动/片段被销毁时,视图模型也将不复存在。所以来自ViewModel的数据将会丢失。那为什么说它会把工作委托给Repository呢?存储库模块只负责获取和更新数据。而不是保存它。所以当活动/片段被销毁时,数据无论如何都会丢失。我是不是忘记了什么?我是不是搞错了什么?
我们可以让存储库模块在内存或数据库中缓存数据,这样下次视图模型要求相同的数据时,我们就不必再次调用web服务。 存储库直接返回缓存的数据。
嗨Lyla Fujiwara!这篇文章是如此的有见地。谢谢你提供如此深刻的知识转移。
但我仍有一个疑问,每当我们旋转屏幕时,我们是在创建一个新的观察对象,还是在使用相同的引用?
谢谢你!惊人的解释:)
拼写错误*。
非常感谢Lyla Fujiwara。
非常感谢Lyla Fujiwara。第一次读你的博客文章,我也看了一些你做的Youtube视频演示。你还在Android基础课程中教过我。现在我已经开始学习Nanodegree课程了。非常感谢。
现在来谈谈我的问题。
我正在开发一个应用程序,它应该从托管在网络上的媒体库中流传音频文件。我被卡住了,所以我想问一下,使用ViewModel + Room是否可以帮助我实现我的任务。如果是的话,我应该如何实现这些组件,使其成为现实。
Lyla Fujiwara我正试图用livedata和视图模型对现有的代码实施新的架构变化,因为我们有TrasactionTooLargeException问题。
我在看了https://www.youtube.com/watch?v=SlZVYkhoSq8之后来到这里,我能够用ViewModel和LiveData重新创建项目。如果有人想看一看学习一下,你可以到这里来 https://github.com/hendrawd/CourtCounter。
你好,我正在使用房间+实时数据,我有一个带有回收器视图的片段(片段A),适配器来自livedata+房间的查询。
现在我把同样的片段扩展到一个对话框中(片段B),但是如果我改变了对话框中的一些东西(并且片段A在前台),片段A就会变成与片段B相同的数据。
我使用viewModel = ViewModelProviders.of(this@FragmentShortcut)[ShortcutViewModel::class.java]。
我有ShortcutViewModel non singleton的匕首,但不知道这是否会影响到一些方式,是否有可能只为这个片段提供实时数据? :()
是的,解决了我的问题。
你好Lyla Fujiwara,你知道是否有办法在片段之间共享AndroidViewModel(含/Application context)吗?
当你说UI控制器时,你是指活动或片段吗?还是这里有某种演示者/控制器逻辑?
我想知道用于进度对话框、警报对话框或Toast的Context可以作为ViewModel的函数参数来传递?
嗨,Lyla Fujiwara,非常感谢你的这些写作。我正在努力升级我的应用程序以使用Android架构组件。从这篇报道中,我看到你是直接从视图中更新ViewModel’的数据。这样做效果非常好。
在我的用例中,我将LiveData与ViewModel结合起来使用。
我有一个片段,正在显示Todo-item的详细信息,并且处于编辑模式。todo-item由ViewModel持有,ViewModel有一个暴露的API来返回片段所观察到的 "LiveData"。当用户进行修改时,我直接从视图中更新这个livedata的值,但我如何处理文本的变化? 例如,用户编辑项目的标题?劫持`EditText`的文本变化回调听起来太黑了。
我也把我的问题更详细地贴在了SO上。请点击这里了解更多细节,如果你想把你的答案粘贴到那里,如果你到了那里。
Nice.....please,你能给我一些例子,说明如何为任何应用程序确定视图组件吗?
你好,谢谢你的精彩介绍。只是想提一下,你需要在build.gradle. implementation "android.arch.lifecycle:viewmodel:1.1.0"
中加入以下一行。
非常好的帖子,非常感谢!:-)
嘿,谁能给我一个下面的例子吗?
“对于你打开的每个不同的游戏屏幕,如果你将ViewModel和GameScoreActivity
中的onCreate
关联起来,它将创建一个不同的ViewModel实例。如果你旋转其中一个屏幕,与相同的ViewModel的连接将被保持。
- 这些指导方针也成为一个很好的方式来认识到其他的人在网上有相同的热情,像我一样,以掌握更多关于这个条件的伟大的交易。
不错的文章!!!@Lyla Fujiwara。这真的很简单。请澄清我的一个疑问。当我们写一些用户数据到服务器或Db时,我们需要使用架构组件吗? 即如果我需要在用户点击按钮时发布一些数据到服务器。请帮助我。
谢谢你对视图模型的解释,非常容易理解。
使用这个比savedInstanceState有什么好处?即使在示例的动画GIF上,我认为使用savedInstanceState实际上是一个很好的案例......我不认为它能在死的活动被重新创建的情况下生存,不是吗? 它至少有较少的限制吗?也许有能力把各种变量放在里面?
Lyla Fujiwara, 我有一个情况,我需要我的子片段影响父活动中的一个 UI。我所想到的解决方案是给子片段一个对父活动’ViewModel的引用。因此,子片段现在有两个ViewModel引用,一个是为它自己做的,另一个是为它的父对象做的。
你认为如何?我希望你能回答这个问题。
这对我帮助很大!谢谢 :)
你好,谢谢你的这个教程,但是请问,你有没有这个例子的git链接,谢谢你的出色工作。
这确实是对ViewModels的出色概述。我现在对它们不再感到恐惧,并期待着在我的应用程序中使用它们,这要感谢你。
很好的教程!继续努力吧:)
很好的文章!你能展示 GameScoreActivity onCreate 的代码吗?我很想知道你是如何让活动将游戏分数ID传递给视图模型的。谢谢!
Lyla你好,我对这个LiveData和Room有点失望。我跟随你的帖子,阅读了架构组件的指南,但我仍然不能让它工作。
我正在将资料活动从加载器迁移到实时数据和房间,以了解它是如何工作的,所以我从LiveData开始。
我创建了我的UserProfileViewModel类,该类有一个MutableLiveData<Profile>,它的getter如果为空则创建一个新的Mutable并将其返回,工作很正常。
我在片段的onActivityCreated中设置了我的订阅方法,很好,它可以正常工作。
我"手动"—为了测试目的—加载数据(它仍然在SQL上而不是房间)在onLoadFinish中我使用postValue来更新我的UserProfileViewModel如果我点击reload,这次观察者抓住了onChange所以它更新了UI,很完美。
然后我旋转了设备,由于一些我不明白的原因,UserProfileViewModel不再包含数据,我调试了一下,我注意到getter被调用了几次,在某些时候它再次初始化了MutableLiveData<Profile>。
如果我理解正确的话,LiveData应该保持活力,并且仍然有我的配置文件对象,但它没有。有什么想法吗,谢谢。我不想放弃。
谢谢你
另一个选择可能是将ViewModel与Activity而不是Fragment相关联。你可以通过将getActivity()
传递给ViewModelProviders
而不是Fragment来做到这一点。
这将允许你重新创建片段,如果你想的话,只要父活动还在,就不会丢失数据。但还是那句话,不确定你的片段重现是一个功能还是一个错误 :)
片段的重新创建是问题所在。我正在开发的应用程序只有纵向模式,在玩这个新的变化时,我只是忘记了验证这个片段是否存在。所以这就是问题所在,每当设备旋转时,片段就会被替换成一个新的实例和新的LiveData。
它能处理各种数据吗? 如果同一片段/活动有多个实例怎么办?如何只为那些应该得到它的人恢复数据?
ViewModels可以容纳任何你喜欢的东西,包括大型媒体文件和数据库连接(虽然这个可能应该放在Repository类中)。有什么特别的数据类型是你所担心的吗?
如果有多个片段或活动,将为每个片段或活动创建一个不同的ViewModel。唯一需要注意的是,如果 "多个片段/活动 "是由配置变更产生的;在这种情况下,ViewModelProvider将技术上的 "新 "活动/片段与预先存在的ViewModel相关联。
另外,关于Fragments,你可以让Fragment获得对父Activity ViewModel的引用,这允许你在不同的Fragments之间共享数据,而不是为每一个创建一个新的ViewModel。这意味着你可以随心所欲地交换Fragments,但数据将由Activity保留。更多信息这里。
嗨,莱拉。
如果我需要使用viewmodel将对象A加载到listview的适配器中,但需要访问对象B(是对象A的孩子)的列表数据,怎么办?用户也可以将更多的对象B添加到列表中的任何对象A中,但所做的任何改变只应在点击保存按钮后持续存在。
What if I have need to load object A using viewmodel into a listview’s adapter, but need to access data of a list of object B (which is child of object A)?
那么,对象B本质上是类似于对象A的评论列表的东西?这取决于你用什么来加载数据。一个简化的版本可能让你只使用Room &mdash;事实上,BasicSample向你展示了如何加载有评论的对象(这是使用Room数据库和LiveData)。
databaseCreator.getDatabase().commentDao().loadComments(mProductId);
The user can also add more object B into any object A in the list, but any changes made should only persisted to once a save button is clicked.
这取决于你的用户界面是怎样的。如果你有一个编辑活动,你可以创建一个对象B的实例,并将其放在编辑活动的ViewModel中,但不将其保存在数据库中。由于 ViewModel 的工作方式,该对象的数据将在配置更改后继续存在,但它不会被持久化(如果用户关闭应用程序则可用),因为你没有将它保存在本地存储中。(如果你希望用户能够离开活动而不丢失数据,请阅读ViewModels和onSaveInstanceState。)
当用户按下保存键时,运行你的代码(脱离主线程),通过将对象B保存在数据库中来持久化你的ViewModel中的对象。
如果你想在你的编辑活动中改变对象B并让用户界面自动更新,那么就使用LiveData对象。你不需要在Room中使用LiveData,LiveData文档中的例子显示了使用String对象的情况。
你好!我刚刚开始使用Android Architect Components,我不明白一件事:为什么不允许在ViewModel类中存储对视图(活动或片段)的引用?文档说,这可能导致内存泄漏。这是真的,但我可以在View的onDestroy()方法中手动清除这个引用,并在View的onCreate()方法中发送新的引用到ViewModel类以防止泄漏。
引用View的ViewModel可以实现Model-View-Presenter的模式(使用ViewModel类作为Presenter)。
示例:https://gist.github.com/Denis-Avenger/2645b0cfaf22cb70b71de40cff59a4f0。
请您解释一下,我能否使用ViewModel类作为引用View的Presenter来避免内存泄漏,我将在View的onCreate()和onDestroy()方法中手动清除和更新该类。
嗨,Lyla Fujiwara。祝贺你的伟大文章。在没有上下文引用的情况下,你如何从视图模型中启动一个活动/片段?
我的想法是,它可以通过回调接口实现,但是如果有多层次的启动活动呢?然后你就会创建一个回调地狱。有没有其他更有效的解决方案的想法?
提前感谢!
我有一个问题Lyla Fujiwara,ViewModel可以和DataBinding一起工作吗?
你不能在同一个类中同时扩展BaseObservable和ViewModel。你可以在ViewModel中使用ObservableField。如果你看一下架构组件BasicSample中的ProductFragment和ProductViewModel,你可以看到在ViewModel中使用LiveData和ObservableField的一个例子。团队正在研究如何使之更简单。
目前的另一个选择是将BaseObservable的代码复制并粘贴到ViewModel的子类中,并从该子类中进行扩展。
注意,自从我写这篇文章以来已经有一段时间了,现在LiveData和ViewModel与数据绑定工作得非常出色。由于你的ViewModel通常包含你的布局所需的所有数据,你可以将你的ViewModel作为数据绑定变量传递给你的布局。如果你使用的是LiveData,当你的LiveData值更新时,调用setLifecycleOwner
将自动神奇地更新数据绑定。
binding.setLifecycleOwner(this)
您可以在文档中了解到更多信息。
Lyla Fujiwara, https://medium.com/@lylalyla/you-canot-extend-both-baseobservable-and-viewmodel-in-same-class-e1cf54255502我们是否有关于能够使用BaseObservable和AndroidViewModel的更新?观察域(ObservableField)是唯一的途径吗?
我对MutableLiveData.class感到困惑,它什么都不做,只调用两个方法。super.postValue()和super.setValue()。
所以LiveData是一个抽象的类。正因为如此,如果你想实际创建一个LiveData对象,你要么需要扩展它,要么使用MutableLiveData这样的类。LiveData也没有提供设置它所包装的数据的方法,但是MutableLiveData提供了postValue和setValue来改变它所持有的数据。当LiveData对象中的数据发生变化时,它会通知所有的观察者。观察是LiveData的真正的@power。希望这对你有帮助!
你好Lyla Fujiwara ,这是一篇非常棒的文章,谢谢👍,我已经参加了你的Android开发者初级班,整个课程对我的入门 和 完成 我的 Nanodegree 也很有帮助。我有一个关于安卓视图模型的问题,是否可以为活动和活动中的片段创建多个视图模型,或者只使用同一个视图模型并创建多个引用?
嗨,萨西!很高兴听到你喜欢Android初学者的ND!
因此,如果你有充分的理由,为一个活动创建多个ViewModel类是可以的。一般的经验法则是,如果你的用户界面的一个部分(一个片段、一个活动等)需要大量的数据,你应该为它创建一个单独的ViewModel类。
例如。如果你有一个具有三个不同片段的活动,你可能想为每个片段创建一个ViewModel,如果它们并不总是同时出现在屏幕上并且不共享数据。
创建一个单一的ViewModel,并让两个不同的UI控制器引用它也是可以的。一个例子是在片段之间共享数据,你可以在文档这里,以及Lifecycle codelab中找到更多信息。
我想不出你为什么要为一个活动创建多个ViewModel实例的用例。API 目前允许通过一个关键参数进行,但我还没有听说有人以这种方式使用它。
很好的文章,用简单的语言澄清了关于ViewModel的一般概念。
嗨,Lyla Fujiwara只是想说我是你的忠实粉丝。我总是喜欢你的教程中的简单性。继续努力吧。