ViewModel 的基本用法

2023-11-03

ViewModel简介

ViewModel 应该算是Jetpack 中最重要的组件之一了。其实Android 平台上之所以会出现注入MVP、MVVM 之类的项目架构,就是因为在传统的开发模式下,Activity 的任务实在是太重了,既要负责逻辑处理,又要控制UI 提示,甚至还得处理网络回调,等等。在一个小项目中这样写或许没有什么问题,但是如果在大型项目中仍然使用这样写法的话,那么这个项目将会变得非常臃肿并且难以维护,因为没有任何架构上的划分。

ViewModel 的一个重要作用就是可以帮助Activity 分担一部分工作,它是专门用于存放于界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity 中,这样可以在一定程度上减少Activity 中的逻辑。

另外,ViewModel 还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候,Activity 会被重新创建,同时存放在Activity 中的数据也会丢失。而ViewModel的生命周期和Activity 不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity 退出的时候才会跟着Activity 一起销毁。因此,将与界面相关的变量存放在ViewModel 当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。ViewModel 的生命周期如图所示:
在这里插入图片描述

ViewModel 的基本用法

由于Jetpack 中的组件通常是以 AndroidX 库的形式发布的,因此一些通常的Jetpack 组件会在创建AndroidX 项目时自动被包含进去。不过如果我们想要使用 ViewModel 组件,还需要在app/build.gradle 文件中添加如下依赖:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

通常来讲,比较好的编程规范是给每一个 Activity 和 Fragment 都创建一个对应的ViewModel ,因此这里我们就位 MainActivity 创建一个对应的 MainViewModel 类,并让它继承自ViewModel ,代码如下所示:

import androidx.lifecycle.ViewModel

class MainViewModel: ViewModel() {
}

根据前面所学的知识,所有与界面相关的数据都应该放在ViewModel 中。那么这里我们要实现一个计数器的功能,就可以在 ViewModel 中加入一个 counter 变量用于计数,如下所示:

class MainViewModel : ViewModel() {
    var counter: Int = 0
}

现在我们需要在界面上添加一个按钮,每点击一次按钮就让计数器加1,并且把最新的计数显示在界面上。修改activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/infoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="32sp" />

    <Button
        android:id="@+id/plusOneBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Plus One" />

</LinearLayout>

布局文件非常简单,一个TextView 用于显示当前的计数,一个Button 用于对计数器加1。

接着我们开始实现计数器的逻辑,修改MainActivity 中的代码,如下所示:

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener{
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter(){
        infoText.text = viewModel.counter.toString()
    }
}

先看下运行结果
在这里插入图片描述

注意:我们绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider 来获取 ViewModel 的实例,具体语法规则如下:

ViewModelProvider(<你的Activity 或 Fragment 实例>).get(<你的ViewModel>::class.java)

之所以这么写,是因为ViewModel 有其独立的生命周期,并且其生命周期要长于Activity 。如果我们在onCreate() 方法中创建一个ViewModel 的实例,那么每次onCreate() 方法执行的时候,ViewModel 都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了

除此之外的其他代码应该都是非常好理解的,我们提供了一个refreshCounter() 方法用来显示当前的计数,然后每次点击按钮的时候对计数器加1,并调用refreshCounter() 方法刷新计数

如果你尝试通过侧边工具栏旋转一下模拟器的屏幕,就会发现Activity 虽然被重新创建了,但是计数器的数据却没有消失

向ViewModel传递参数

上一小节中创建的 MainViewModel 的构造函数中没有任何参数,但是思考一下,如果我们确实需要通过构造函数来传递一些参数,应该怎么办呢?由于所有ViewModel 的实例都是通过ViewModelProvider 来获取的,因此我们没有任何地方可以向ViewModel 的构造函数中传递参数

当然,这个问题也不难解决,只需要借助ViewModelProvider.Factory 就可以实现了

现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了。接下来我们就对这一功能进行升级,保证即使在退出程序后又重新打开的情况下,数据仍然不会丢失。

相信你已经猜到了,实现这个功能需要在退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,传递给MainViewModel 。因此,这里修改 MainViewModel 中的代码,如下所示:

class MainViewModel(countReserved: Int) : ViewModel() {
    var counter: Int = countReserved
}

现在我们给 MainViewModel 的构造函数添加了一个 countReserved 参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给 counter 变量

前面已经说了需要借助 ViewModelProvider.Factory ,因此新建一个 MainViewModelFactory 类,并让它实现 ViewModelProviders.Factory 接口,代码如下所示:

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved) as T
    }
}

可以看到,MainViewModelFactory 的构造函数中也接收了一个 countReserved 参数。另外 ViewModelProvider.Factory 接口要求我们必须实现create() 方法,因此这里在create() 方法中我们创建了 MainViewModel 实例,并将 countReserved 参数传了进去。为什么这里就可以创建 MainViewModel 的实例了呢?因为create() 方法的执行时机和 Activity 的生命周期无关,所以不会产生之前提到的问题

另外,我们还得在界面上添加一个清零按钮,方便用户手动将计数器清零。修改activity_main.xml 中的代码,(在原来基础上增加一个按钮即可)如下所示:

<Button
        android:id="@+id/clearBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Clear"
        />

修改 MainActivity 中的代码

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: MainViewModel
    private lateinit var sp:SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sp = getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved",0)

        viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener{
            viewModel.counter++
            refreshCounter()
        }
        clearBtn.setOnClickListener {
            viewModel.counter = 0
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter(){
        infoText.text = viewModel.counter.toString()
    }

    override fun onPause() {
        super.onPause()
        sp.edit{
            putInt("count_reserved",viewModel.counter)
        }
    }
}

现在重新运行程序,点击数次”Plus One“ 按钮,然后退出程序并重新运行,你会发现,计数器的值是不会丢失的,只有点击”Clear“ 按钮,计数器的值才会被清零。如图所示:
在这里插入图片描述

本章内容源自 郭霖大神的《第一行代码 第三版》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ViewModel 的基本用法 的相关文章

  • ASP.NET MVC 2 验证应该去哪里:模型还是视图模型类?

    我正在使用自动映射器将我的模型映射到视图模型类以传递到我的视图 我的问题实际上是验证应该去哪里 我计划使用元数据装饰 mvc 2 的一个功能 但无论是在模型中还是在视图模型中 还是两个地方都有 验证应该至少在视图模型中完成 因为这是您作为操
  • 当前的 MVVM 视图模型实践是否违反了单一职责原则?

    根据当前的实践 至少在 WPF 和 Silverlight 中 我们可以看到视图模型中通过命令绑定来绑定视图 或者至少可以看到视图模型中处理的视图事件 这似乎违反了SRP http en wikipedia org wiki Single
  • WinRT ViewModel DataBind 到异步方法

    我正在反序列化 XML 文件中的对象列表 并希望通过 ViewModel 传递到我的视图中这些对象的实际内容 问题是文件操作是async并且它一直冒泡到 ViewModel 其中 Property getters 不能被标记为这样 Prob
  • 无法获取提供程序 androidx.lifecycle.ProcessLifecycleOwnerInitializr

    运行具有依赖项的应用程序时出现错误 androidx lifecycle lifecycle extensions 2 1 0 alpha03 and androidx lifecycle lifecycle viewmodel 2 1 0
  • 在域模型之间映射数据的模式

    这是我最近需要做的一件常见的事情 我正在寻找任何常见的模式来使这变得更容易一些 这一切的主要要点是我有一些数据模型 它们被建模来满足 ORM 并纯粹对对象进行 CRUD 操作 这些模型目前通过存储库 工厂公开 取决于其 C 还是 RUD 然
  • MVVM - 从 ViewModel 后面的代码中调用 UI 逻辑

    我正在使用 MVVM 模式开发一些 Net XAML 应用程序 根据 MVVM 我将应用程序逻辑保留在 VM 中 并在代码隐藏中执行与 UI 相关的操作 但我需要在Code Behind中执行一些UI相关的代码来响应VM中的一些逻辑 例子
  • 更改默认值“{0} 字段为必填项”(最终解决方案?)

    再会 我有以下用于登录表单的 ViewModel 类 using System ComponentModel DataAnnotations public class UserLogin IDataErrorInfo Required Di
  • 如何观察数据库的变化以更新LiveData

    我正在从以下位置迁移应用程序LoaderManager with Callbacks到一个实现使用ViewModel and LiveData 我想继续使用现有的SQLiteDatabase 主要实现工作正常 这Activity实例化Vie
  • 如何使用 Silverlight 和 MVVM 设计复合视图和视图模型?

    我想在我的 Silverlight MVVM 应用程序中创建一个 向导 该向导应包含多个步骤 您可以使用 下一个 和 上一个 在这些步骤之间导航 我面临的问题是视图和视图模型之间的关系 我希望向导本身有一个视图和视图模型 我的直觉告诉我 向
  • MVC 中的 ViewModel 和与实体框架的一对多关系?

    我有一个用于在数据库中存储有关顾问的信息的应用程序 该模型是一个实体框架模型 数据库表与许多其他表 工作经验 计划 能力区域等 具有一对多关系 现在 当我想在视图中创建一个新的 Consultant 对象时 我实际上只想将 Consulta
  • 模型和实体有什么区别[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我很困惑不明白这句话的含义 Entity Model DataModel ViewModel 任何人都可以帮助我理解它们吗 这些术语的
  • 通用视图模型?

    我想知道尝试创建一个采用通用视图模型的视图是否是一种好的做法 我想知道这一点 因为有人提到他预计必须执行大量重复代码 除非他开始制作通用视图和通用视图模型 所以基本上视图就像一组控件 一个视图可能有 2 个控件 例如文本框和单选按钮 另一个
  • 显示一个子虚拟机,然后在第一个关闭后显示另一个子虚拟机

    我有一位家长指挥 我想显示其中的第一个视图模型 然后在第一个关闭后 即完成一些操作 我想显示一个不同的视图模型 我在用着Caliburn Micro Contrib https github com kmees CMContrib 其中一个
  • 聚合 ViewModel 是个好主意吗?

    将一个 ViewModel 作为另一个 ViewModel 的属性是好还是坏主意 例如 public sealed class ContentManagerViewModel ViewModel public FindViewModel F
  • 为什么有两个类:视图模型和域模型?

    我知道使用域模型作为视图模型可能很糟糕 如果我的域模型有一个名为 IsAdmin 的属性 并且我有一个创建控制器操作来创建用户 那么有人可以更改我的表单并使其 POST IsAdmin true 表单值 即使我没有在视图中公开这样的文本字段
  • 在 ASP.NET MVC ViewModel 中存储模型 ID,安全问题

    在我的 MVC 应用程序中 我有一个页面供用户编辑其帐户详细信息 例如电子邮件地址 密码等 在我的数据库中 用户表保存此数据 主键是 UserId 在我创建的 ChangeAccountDetails 视图上 我传递了一个 ViewMode
  • 在 mvc3 中公开实体或 DTO 以查看的最佳实践是什么?

    我创建了自己定制的大量架构 包括针对不同技术的 n 层 目前正在使用 asp net mvc 框架进行 n 层架构 问题是我在数据访问层有实体框架 由于实体将拥有所有关系元数据和导航属性 因此它变得更重 我觉得直接通过 mvc 视图公开这些
  • WPF MVVM 在窗口关闭时调用 ViewModel Save 方法

    我已经弄清楚如何从我的 ViewModel 关闭窗口 现在我需要从另一侧解决窗口关闭问题 当用户单击窗口的关闭按钮时 我需要在 ViewModel 中触发 Save 方法 我正在考虑将 Command 属性绑定到 Window 的关闭事件
  • 在 MVVM 中,可以在视图后面的代码中访问 ViewModel 吗?

    在 MVVM 模式中 是否可以接受甚至可以访问视图代码后面的 ViewModel 属性 我有一个可观察的集合 它填充在 ViewModel 中 我需要在视图中使用它来绑定到带有链接列表的无限滚动条 IE private LinkedList
  • MVVM 创建 ViewModel

    有人可以向我解释一下如何为 MVVM 模式创建 ViewModel 我试图理解这里的教程 http msdn microsoft com en us magazine dd419663 aspx http msdn microsoft co

随机推荐

  • 【源码分析】zeebe actor模型源码解读

    zeebe actor 模型 如果有阅读过zeebe 源码的朋友一定能够经常看到actor run 之类的语法 那么这篇文章就围绕actor run 方法 说说zeebe actor 的模型 环境 zeebe release 8 1 14
  • Java统一返回结果自动封装组件【Response-boxing】

    0 需求 统一封装返回结果 包括code message data数据 不用手动封装 通过自定义注解标记即实现封装 如果controller结果已经手动封装 则不重复封装 1 项目结构 2 创建自定义注解 import java lang
  • Paxos算法的java实现demo(只是为了简单的测试)

    Paxos 的概念我就不在这里啰嗦了 网上有很多优秀的博客 下面是我推荐的一个写的比较好的 https www cnblogs com linbingdong p 6253479 html 我们直接上代码吧 代码里面都有注释 先看一下项目结
  • 基于mulitisim14仿真的数字电子称

    参考了下面的文章做了一个数字电子称 https www renrendoc com paper 119413660 html 仿真如下 需要仿真文件的私聊
  • 中国工程院院士郑纬民:元宇宙是一个赋能实体经济的重要新赛道

    2022年3月31日 元宇宙产业委共同主席郑纬民院士在第三届元宇宙产业论坛发表了题为 元宇宙创新应用全面启航 算力是基础 的演讲 以下为郑纬民院士的演讲全文 今年全国两会中一些代表和委员提出了关于元宇宙的建议和提案 说明元宇宙已经得到了大家
  • 吉林大学超星MOOC学习通高级语言程序设计 C++ 实验04 数组及其在程序设计中的应用(2021级)(1)

    1 索引数组排序 题目编号 Exp04 Enhance04 GJBook3 06 21 题目名称 索引数组排序 题目描述 已知n n 100 个元素的整型数组 A 未排序 一个索引数组 B 保存 A 的下标 编写程序 在不改变数组A的情况下
  • Unikernels 解读

    转载于https zhuanlan zhihu com p 29053035 Unikernels Beyond Containers to the Next Generation of Cloud是 Russ Pavlicek的一本动物书
  • (Animator详解二)Unity Animator的基本属性

    在Inspector下 Animator的第一项为状态机的名称 注意 这里的名称不是动画名称 Tag 当前动画的Tag标签 可以通过Tag值来处理一些逻辑 Motion 动画片段的名称 Speed 动画的播放速度 1表示正常播放 speed
  • spring一些捞到的东西

    spring指令重排和多线程 原来在编写程序的时候要考虑这么多东西 要想清楚每一个代码 每一个线程在哪执行 还有要懂得jvm 的一些优化的 任重而道远啊 单例模式 只允许一个实例的存在 构造函数是私有的 对外提供获取实例的方法 getIns
  • CSS -网页动画

    目录 制作网页动画 1 CSS变形 2 CSS过渡 3 CSS动画 4 总结 制作网页动画 1 CSS变形 CSS3变形是一些效果的集合 如平移 旋转 缩放 倾斜效果 每个效果都可以称为变形 transform 它们可以分别操控元素发生平移
  • 第七十六篇 MIPI简单说明

    MIPI 移动行业处理器接口 是Mobile Industry Processor Interface的缩写 MIPI是MIPI联盟发起的为移动应用处理器制定的开放标准 目的是把手机内部的接口如摄像头 显示屏接口 射频 基带接口等标准化 从
  • c++之重载函数学习总结

    一 C 中的函数重载 1 函数重载的概念 用同一个函数名定义不同的函数 当函数名和不同的参数搭配时函数的含义不同 注意 在c语言中是没有函数重载这个概念的 代码示例演示 include
  • 用Flutter实现GaiaControl BLE OTA升级功能,支持Android/IOS

    代码基本移植官方GaiaControl Demo 支持RWCP 断点续传 设置蓝牙mtu 协议 这里主要分析GAIA CSR ble ota的过程 协议等等 希望对你有所帮助 这里对蓝牙服务特性订阅都不谈 读者自行了解 Gaia 是CSR
  • DM8锁查询及解决

    锁模拟 session1 与 session2同时对表t2的col1 200的列进行更新 但不提交 session1 SQL gt create table t1 col1 int SQL gt create table t2 col1 i
  • Select For update语句浅析

    Select forupdate语句是我们经常使用手工加锁语句 通常情况下 select语句是不会对数据加锁 妨碍影响其他的DML和DDL操作 同时 在多版本一致读机制的支持下 select语句也不会被其他类型语句所阻碍 借助for upd
  • chi square-卡方分布的定义及性质

    chi square 卡方分布的定义及性质 摘要 2 chi 2 2分布 卡方分布 的定义 g
  • Anchor-Free即插即用

    点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 后台回复 多模态综述 获取论文 后台回复 ECCV2022 获取ECCV2022所有自动驾驶方向论文 后台回复 领域综述 获取自动驾驶全栈近80篇综述论文 Anchor
  • 做什么副业比较靠谱,这五个正规项目,记得收藏

    人这一生不易 每个阶段都会有压力和烦恼 尤其是成年人 上有老下有小的 生活的重担都在一个人身上 压得人喘不过气 生活的方方面面都需要钱 仅靠工资已经很难维持一家人的开支了 所以很多人打算利用业余时间做点副业 来增加收入 可是不知道做什么 哪
  • Roaming\npm\node_modules\nrm\node_modules\open\index.js:38

    nrm1 2 1版本安装遇到的问题 C Users Cwqiang gt nrm ls C Users Cwqiang AppData Roaming npm node modules nrm node modules open index
  • ViewModel 的基本用法

    文章目录 ViewModel简介 ViewModel 的基本用法 向ViewModel传递参数 ViewModel简介 ViewModel 应该算是Jetpack 中最重要的组件之一了 其实Android 平台上之所以会出现注入MVP MV