ViewModel的创建

ViewModel本身只是ViewModel这个类的子类:

class MainViewModel: ViewModel() {
}

在屏幕旋转UI重建的时候, 它是如何拥有保持数据的能力的呢? 它又是何时被清理的呢?

答案全跟它是如何创建, 保存的有关系.

本文回顾一下创建ViewModel的几种常见写法.

注: 本文中的图并不是严格意义的时序图(也不符合规范), 只是为了简略表示一下代码中的调用关系.

原生手动创建ViewModel

当ViewModel没有构造参数

当ViewModel没有参数的时候很简单:

class MainViewModel: ViewModel() {}

class MainActivity : ComponentActivity() {

    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
}

当然不是随便就new出来的啦, 得通过ViewModelProvider来get.

注意: 这句不能在Activity的onCreate()之前调用, 也意味着你不能声明字段直接赋值.

否则你就会得到这个报错:

Caused by: java.lang.IllegalStateException: Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.

此时的方法调用大概是这样:

从这里我们看到ViewModelProvider这个工具人要借助其他两个东西来提供ViewModel:

  • ViewModelStore: 负责存储ViewModel.
  • Factory: 负责实例化具体的ViewModel类型.

    请记住这两个知识点, 后面要考.

在上面这个最简单的例子中:

  • Activity是ViewModelStoreOwner, 它可以getViewModelStore()

    ViewModelStoreOwner(比如Activity)因为configuration changes重建, 新的owner仍然会get这个旧的ViewModelStore实例.
  • 我们没有传Factory, 所以最终用的是没有参数的NewInstanceFactory.

当ViewModel有构造参数

但是通常, ViewModel会需要一些依赖, 我们就需要从构造传入一些参数

class MainViewModel(
private val repository: MainRepository,
) : ViewModel()

此时我们的工厂就需要自己实现了:

我们需要把依赖对象传给工厂, 好让它构造ViewModel的时候能用上:

class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

接着把我们的工厂传给ViewModelProvider:

class MainActivity : ComponentActivity() {

    private lateinit var viewModel: MainViewModel
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository) override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
}
}

此时的过程大概是这样:

AndroidX来帮忙

感谢AndroidXactivity-ktx(fragment-ktx里也有Fragment版本的)包里的by viewModels()属性代理,

我们上述的代码可以简化成这样:

class MyViewModelFactory constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
} class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
private val repository: MainRepository = MainRepository()
private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository) override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // use viewModel
}
}

工厂依然自己写, 简化了provider get的部分:

  • 扩展方法, 隐含activity对象.
  • lazy规避了生命周期的问题, 只要使用ViewModel的地方不在onCreate之前就行.

ViewModel没有参数的时候更简单:

private val viewModel: MainViewModel by viewModels()

Dagger时代

有了dagger, 我们可以在构造上标记@Inject告诉dagger帮我们创建repository和viewModelFactory:

class MainRepository @Inject constructor(){}

@Singleton
class MyViewModelFactory @Inject constructor(private val repository: MainRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

但是我们却不能对ViewModel这样做, 因为ViewModel应该被存储在ViewModelStore里, 而它是由activity提供的.

我们会注入viewModelFactory, 然后用它来创建ViewModel:

class MainActivity : ComponentActivity() {

    private val viewModel: MainViewModel by viewModels {
viewModelFactory
}
@Inject
lateinit var viewModelFactory: MyViewModelFactory
}

因为你用的是dagger, 你会需要在onCreate()里写类似这样的东西:

 (applicationContext as MyApplication).appComponent.inject(this)

这里只是用DI框架简化了依赖和工厂的构造.

Hilt时代

在ViewModel上标记: @HiltViewModel, 构造标记:@Inject:

@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: MainRepository,
) : ViewModel() {
}

依赖们也可以构造标记@Inject:

class MainRepository @Inject constructor(){}

在Activity加上注解@AndroidEntryPoint,

也用了by viewModels():

@AndroidEntryPoint
class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // use viewModel
}
}

请注意, 和dagger不同的是, 这里并不需要注入工厂(也不需要写工厂), Hilt用的是自己的工厂HiltViewModelFactory.

这个过程:

Compose的ViewModel()方法

在Compose中获取一个ViewModel非常简单, 可以只用一个viewModel()方法.

(这个方法在androidx.lifecycle:lifecycle-viewmodel-compose依赖包里).

@Composable
fun Greeting(name: String) {
val viewModel: MainViewModel = viewModel()
// use viewModel
}

Activity的onCreate()中setContent是Compose, 于是在这里就设置好各种owners,

与ViewModel相关的就是这个LocalViewModelStoreOwner, 之后包在里面的内容就可以随时获取owner, 得到ViewModelStore和ViewModel了.

Compose的hiltViewModel()方法

上面的viewModel()方法不管在哪里获取, ViewModel的scope都是和当前的Activity或Fragment绑定.

假如我们有多个composable的界面呢?

使用hiltViewModel()这个composable方法我们可以获取到一个scope到某个导航目的地的ViewModel.

(这个方法在androidx.hilt:hilt-navigation-compose依赖包里).

NavHost(navController = navController, startDestination = "friendslist") {
composable("friendslist") {
val viewModel = hiltViewModel<MainViewModel>()
FriendsList(viewModel = viewModel, navHostController = navController)
}
...
}

这种方式获取的ViewModel, 它的生命周期是和这个导航目的地相关的, 当退出这个界面, ViewModel就被clear, 下次再进就又是新的对象.

此时的owner终于不再是Activity或Fragment, 而是NavBackStackEntry.

导航这个话题就先不展开这里讲了.

总结

ViewModel的创建很关键, 关系到它的生命周期.

手动创建比较麻烦, 很多样板代码.

本文总结了几种常见的创建方式, 希望读者看完后能有更清晰的理解, 每种方式都是怎么回事, 那些方便的工具替我们做了什么.

ViewModel的创建的更多相关文章

  1. 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](五)

    前言 Hi,大家好,我是Rector 时间飞逝,一个星期又过去了,今天还是星期五,Rector在图享网继续跟大家分享系列文本:一步一步创建ASP.NET MVC5程序[Repository+Autof ...

  2. [ExtJS5学习笔记]第十六节 Extjs5使用panel新增的ViewModel属性绑定数据

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/39078627 sencha官方API:http://docs.sencha.com/e ...

  3. MVC 创建强类型视图

    •在ViewModel中创建一个类型 •在Action中为ViewData.Model赋值 •在View中使用"@model类型"设置 14 手动创建强类型视图 •在ViewMod ...

  4. 转 Android Lifecycle、ViewModel和LiveData

    转自:https://www.jianshu.com/p/982545e01d0a 1.概述 在I / O '17的时候,其中一个重要的主题是Architecture Components.这是一个官 ...

  5. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  6. ASP.NET MVC系列:Model

    1. Model任务 Model负责通过数据库.AD(Active Directory).Web Service及其他方式获取数据,以及将用户输入的数据保存到数据库.AD.Web Service等中. ...

  7. KnockoutJS 3.X API 第八章 映射(mapping)插件

    Knockout旨在允许您将任意JavaScript对象用作视图模型. 只要一些视图模型的属性是observables,您可以使用KO将它们绑定到您的UI,并且UI将在可观察属性更改时自动更新. 大多 ...

  8. MVC入门第一天

    一.异步的两种方法 用jQ的异步  返回content controllor:return Content(sum.ToString());//这里涉及到一个自动封装的问题 html页:<for ...

  9. YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)

    程序=数据结构+算法,而企业级的软件=数据+流程,流程往往千差万别,客户自身有时都搞不清楚,随时变化的情况更是家常便饭,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工作造成很大 ...

随机推荐

  1. 力扣 - 剑指 Offer 10- I. 斐波那契数列

    题目 剑指 Offer 10- I. 斐波那契数列 思路1(递归 / 自顶向下) 这题是很常见的一道入门递归题,可以采用自顶向下的递归方法,比如我们要求第n个位置的值,根据斐波那契数列的定义fib(n ...

  2. SpringMVC学习笔记---依赖配置和简单案例实现

    初识SpringMVC 实现步骤: 新建一个web项目 导入相关jar包 编写web.xml,注册DispatcherServlet 编写springmvc配置文件 接下来就是去创建对应的控制类 , ...

  3. java番外茶余饭后闲聊

    java番外茶余饭后闲聊 **本人博客网站 **IT小神 www.itxiaoshen.com 今天聊点题外话没事时可以作为平时沟通交流的谈资,接下来一起简单了解下个人知晓对Java界开发产生深远影响 ...

  4. 【树莓派】Python开发工控机急停设计

    背景 我们在一些工业产品中使用树莓派替代了PLC和上位机,并借助树莓派的算力将AI和机器视觉引入工业领域. 以前的产品都不存在动作机构,仅仅将结果输出到指示灯.蜂鸣器或者显示器上,没有安全隐患, 现在 ...

  5. 洛谷 P6783 - [Ynoi2008] rrusq(KDT+势能均摊+根号平衡)

    洛谷题面传送门 首先显然原问题严格强于区间数颜色,因此考虑将询问离线下来然后用某些根号级别复杂度的数据结构.按照数颜色题目的套路,我们肯定要对于每种颜色维护一个前驱 \(pre\),那么答案可写作 \ ...

  6. FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅲ

    第三波,走起~~ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ 单位根反演 今天打多校时 1002 被卡科技了 ...

  7. P3438 [POI2006]ZAB-Frogs

    P3438 [POI2006]ZAB-Frogs 给出一个不一样的解法.不需要用到斜率优化等高级算法. 下文记 \(n=w_x,m=w_y\). 首先,答案显然满足可二分性,因此二分答案 \(d\in ...

  8. Jvarkit : Java utilities for Bioinformatics

    Jvarkit : Java utilities for Bioinformatics :一个java写的生物信息工具包:http://lindenb.github.io/jvarkit/

  9. R包 tidyverse 分列

    代码: 1 library(tidyverse) 2 separate(data = df,col=chr_pos,into=c("chr","pos"),se ...

  10. 学习java 7.27

    学习内容: 创建树 Swing 使用JTree对象来代表一棵树,JTree树中结点可以使用TreePath来标识,该对象封装了当前结点及其所有的父结点. 当一个结点具有子结点时,该结点有两种状态: 展 ...