Android笔记(十一):Compose中使用ViewModel

通过ViewModel组件用于保存视图中需要的数据。ViewModel主要目的是将与用户界面相关的数据模型和应用程序的逻辑与负责实际显示和管理用户界面以及与操作系统交互的代码分离开来,为UI界面管理数据。常见的管理方式主要有:LiveData和StateFlow两种形式来实现的。在下列将结合一个简单字符串加密和解密的应用来说明ViewModel管理数据的过程。

一、ViewModel的配置

在项目模块的build.gradle.kt中增加依赖,内容如下:

通过ViewModel可以发现:

(1)ViewModel可以持久保留界面状态。
(2)可以提供对业务逻辑的访问权限

二、加密解密应用简介

加密解密的简单应用如图1和图2的界面展示的。通过在文本框中输入字符串,然后可以点击加密,则可以将文本框中的字符串加密并显示输出。也可以在文本框中输入密文,然后点击解密按钮,则将文本框中的密文进行解密显示。
在这里插入图片描述
图1 加密
"解密的结果:"+
图2 解密

三、利用界面自定义状态控制和存在的问题

在介绍LiveData和StateFlow之前,来了解一下ViewModel只定义业务逻辑,界面中的数据仍然是有界面自行管理形式。

1.定义ViewModel类

class MyCyperViewModel: ViewModel(){/*** 使用Base64加密* @param content String* @return String*/fun encodeByBase64(content:String):String{val bytes = content.encodeToByteArray()return String(Base64.encode(bytes,Base64.DEFAULT))}/*** 使用Base64解密* @param cyperContent String* @return String*/fun decodeByBase64(cyperContent:String):String=String(Base64.decode(cyperContent,Base64.DEFAULT))
}

在自定义ViewModel类MyCyperViewModel中,利用Base64实现了加密和解密的两个函数encodeByBase64和decodeByBase64的业务逻辑。在这个MyCyperViewModel类中并没有定义任何属性数据,并没有对数据进行处理。

2.定义界面

下列代码展示了加密和解密解密的定义:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(cyperViewModel:MyCyperViewModel = viewModel()){var input by remember{mutableStateOf("")}var output by remember{mutableStateOf("")}Box(modifier = Modifier.fillMaxSize().background(Color.Black).padding(top = 30.dp)){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally){Text("加密和解密的简单应用",fontSize = 30.sp,color = Color.White)TextField(value = input,onValueChange = {it:String->input = it})Row(horizontalArrangement = Arrangement.Center,modifier = Modifier.fillMaxWidth()) {Button(onClick = {//加密output = "加密的结果:"+cyperViewModel.encodeByBase64(input)}) {Text("加密")}Button(onClick={//解密output = "解密的结果:"+cyperViewModel.decodeByBase64(input)}){Text("解密")}}if(output.isNotBlank())Text("${output}",fontSize = 30.sp,color = Color.White)}}
}

从代码中可以发现,输入到文本输入框中的状态数据input和显示结果的文本中的数据output都是由界面自行来管理的。通过点击不同的按钮,实现加密和解密的处理。

3.在主活动MainActivity中调用

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ForCourseTheme {Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {HomeScreen()}}}}
}

4.存在问题

在上述实现的过程中,发现了一个问题,当旋转手机(模拟器)时,原有的运行结果中所有输入的数据和输出的结果都会丢失,如图3所示。
在这里插入图片描述

图3 旋转手机(模拟器)界面中的状态数据丢失。
这是因为,旋转手机(模拟器),主活动MainActivity实例会被系统杀死,然后再创建一个新的MainActivity主活动的实例,则导致原有的状态数据丢失,无法再界面中显示,带来的问题就时运行效果不连续和完整。

四、ViewModel结合LiveData和生命周期Lifecycle来管理数据

LiveData是一种可观察的数据存储器类。LiveData的与其他数据存储器不同在于,它具有生命周期感知能力。这意味着LiveData可以遵循如 activity、fragment 或 service这些应用组件的生命周期,来感知应用组件的状态或数据是否发生变化。使得 LiveData 成为更新活跃生命周期状态的应用组件的观察者。

1.重新定义ViewModel

修改上述的CyperViewModel

class MyCyperViewModel: ViewModel(){val output: MutableLiveData<String> = MutableLiveData<String>()/*** 使用Base64加密* @param content String* @return String*/fun encodeByBase64(content:String){val bytes = content.encodeToByteArray()output.value = "加密的结果:"+String(Base64.encode(bytes,Base64.DEFAULT))}/*** 使用Base64解密* @param cyperContent String* @return String*/fun decodeByBase64(cyperContent:String){output.value ="解密的结果:"+String(Base64.decode(cyperContent,Base64.DEFAULT))}
}

在MyCyperViewModel增加了一个MutableLiveData可变的对象output,修改加密和解密的函数,使得加密和解密的结果保存在这一个可变的LiveData对象output中。

2.修改界面

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(cyperViewModel:MyCyperViewModel = viewModel()){var input by remember{mutableStateOf("")}var output1 by remember{mutableStateOf("")}val context = LocalContext.current as MainActivityBox(modifier = Modifier.fillMaxSize().background(Color.Black).padding(top = 30.dp)){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally){Text("加密和解密的简单应用",fontSize = 30.sp,color = Color.White)TextField(value = input,onValueChange = {it:String->input = it})Row(horizontalArrangement = Arrangement.Center,modifier = Modifier.fillMaxWidth()) {Button(onClick = {//加密cyperViewModel.encodeByBase64(input)cyperViewModel.output.observe(context){output1 = it}}) {Text("加密")}Button(onClick={//解密cyperViewModel.decodeByBase64(input)cyperViewModel.output.observe(context){output1 = it}}){Text("解密")}}if(!output1.isNullOrBlank())Text(text = "${output1}",fontSize = 30.sp,color = Color.White)}}
}

在界面中增加了context这个MainActivity的对象实例,把它作为生命周期的拥有者,使得MyCyperViewModel对象实例cyperVIewModel可以感知MainActivity实例的变化。因此,点击按钮实现加密和解密增加如下的处理:

cyperViewModel.output.observe(context){
output = it
}

cyperViewModel的LiveData对象output观察生命周期拥有者context(表示主活动)是否发生变化,一旦发生变化,通知观察者修改界面的状态值output1的值。由于界面的状态值仍是由界面自行处理,ViewModel对象的状态值只是作为观察数据是否变化,来修改界面的状态值而已,因此当旋转手机(模拟器)时,数据仍然会丢失。如果将下列的代码:

Text(text = “${output1}”,fontSize = 30.sp,color = Color.White)

替换成:

Text(text = “${cyperViewModel.output.value}”,fontSize = 30.sp,color = Color.White)

可以非常糟糕地发现,显示结果为null。这是因为cyperViewModel.output.value是一个值,这个值并不具备更新界面的能力。也就是MyCyperViewModel增加了一个可变的LiveData,这个LiveData只是作为感知变化的观察者而存在。

五、使用StateFlow来管理ViewModel中数据

StateFlow是一个状态容器可观察数据流,可向其收集器发出当前状态更新和新状态更新。也可以通过它的value属性读取当前状态。

1.修改ViewModel类增加StateFlow管理状态数据

class MyCyperViewModel: ViewModel(){//私有量只能内部修改数据private val _output = MutableStateFlow("")//获取StateFlow,只能只读val output = _output.asStateFlow()var input by mutableStateOf("")/*** 修改输入状态的值* @param content String*/fun changeText(content:String){input = content}/*** 使用Base64加密* @param content String* @return String*/fun encodeByBase64(content:String){val bytes = content.encodeToByteArray()_output.value = "加密的结果:"+String(Base64.encode(bytes,Base64.DEFAULT))}/*** 使用Base64解密* @param cyperContent String* @return String*/fun decodeByBase64(cyperContent:String){_output.value ="解密的结果:"+String(Base64.decode(cyperContent,Base64.DEFAULT))}
}

在上述代码中定义了一个可变的MutableStateFlow对象_output,将它作为私有量,可以在视图组件内容修改状态的数据。然后提供一个根据这个可变的_output,获取不可变的状态流StateFlow对象output,通过这个状态容器可观察数据流的变化,为界面提供数。另外定义了一个可变的状态input,定义了changeText()函数,用它来跟踪界面输入值的变化。

2.修改界面

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(cyperViewModel:MyCyperViewModel = viewModel()){val output = cyperViewModel.output.collectAsState()Box(modifier = Modifier.fillMaxSize().background(Color.Black).padding(top = 30.dp)){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally){Text("加密和解密的简单应用",fontSize = 30.sp,color = Color.White)TextField(value = cyperViewModel.input,onValueChange = {it:String->//修改输入值cyperViewModel.changeText(it)})Row(horizontalArrangement = Arrangement.Center,modifier = Modifier.fillMaxWidth()) {Button(onClick = {//加密cyperViewModel.encodeByBase64(cyperViewModel.input)}) {Text("加密")}Button(onClick={//解密cyperViewModel.decodeByBase64(cyperViewModel.input)}){Text("解密")}}if(!output.value.isNullOrBlank())Text(text = "${output.value}",fontSize = 30.sp,color = Color.White)}}
}

修改的HomeScreen可组合函数中,原有的状态值已经删除,只定义了:

val output = cyperViewModel.output.collectAsState()

用output来收集cyperViewModel.output状态流对象中保存的状态。
在这里插入图片描述
图4
运行结果可以发现在视图组件中结合StateFlow管理数据,数据不会再丢失。可以很好地完成ViewModel视图组件的主要两个任务:

(1) 持久保留界面状态。
(2) 可以提供对业务逻辑的访问权限。

参考文献:

1.LiveData概览
https://developer.android.google.cn/topic/libraries/architecture/livedata?hl=zh-cn
2.MutableStateFlow
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/179494.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis常见风险分析

击穿 概念&#xff1a;在Redis获取某一key时, 由于key不存在, 而必须向DB发起一次请求的行为, 称为“Redis击穿”。 引发击穿的原因&#xff1a; 第一次访问恶意访问不存在的keyKey过期 合理的规避方案&#xff1a; 服务器启动时, 提前写入规范key的命名, 通过中间件拦截对…

BUUCTF FLAG 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 注意&#xff1a;请将 hctf 替换为 flag 提交&#xff0c;格式 flag{} 密文&#xff1a; 下载附件&#xff0c;得到一张.png图片。 解题思路&#xff1a; 1、因为附件是一张图片&#xff0c;先放到StegSolve中&…

CentOS 7使用RPM包安装MySQL5.7

目标 本文目标是简单介绍如何在CentOS 7上使用RPM包安装MySQL 5.7&#xff0c;然后描述如何调整存储路径datadir。 环境准备 操作系统 —— CentOS 7MySQL版本 —— MySQL 5.7.44 获取MySQL-rpm包 官网下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.htm…

mysql之事务

1、事务的定义 事务是一种机制&#xff0c;一个操作序列&#xff0c;包含了一组数据库的操作命令&#xff0c;所有命令都是一个整体&#xff0c;作为一个整体向系统提交或者撤销的操作&#xff0c;要么都执行&#xff0c;要么都不执行&#xff0c;是一个不可分割的单位 2、事…

Modelsim 使用教程(3)——Projects

目录 一、概述 二、设计文件及tb 2.1 设计文件 counter.v 2.2 仿真文件 tcounter.v 三、操作流程 3.1 Create a New Project&#xff08;创建一个新的工程&#xff09; 3.2 Add Objects to the Project&#xff08;把代码加入项目&#xff09; 3.3 Compile the …

【44.全排列Ⅱ】

目录 一、题目描述二、算法原理三、代码实现 一、题目描述 二、算法原理 三、代码实现 class Solution { public:vector<vector<int>> ret;vector<int> path;vector<bool> check;vector<vector<int>> permuteUnique(vector<int>&am…

winscp文件增量同步到linux服务器

一&#xff0c;点击同步 场景&#xff1a;在做服务器迁移的时候&#xff0c;文件好几十个G一天也迁移不完&#xff0c;每天还有增量的文件&#xff0c;先全量同步一次&#xff0c;然后再用增量同步&#xff0c;然后你用winscp的同步工具&#xff0c;进增量同步。 将本地文件同…

k8s 资源预留

KUBERNETES资源管理之–资源预留 Kubernetes 的节点可以按照 Capacity 调度。node节点本身除了运行不少驱动 OS 和 Kubernetes 的系统守护进程&#xff0c;默认情况下 pod 能够使用节点全部可用容量&#xff0c; 除非为这些系统守护进程留出资源&#xff0c;否则它们将与 pod 争…

BUUCTF 另外一个世界 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;解压得到一个.jpg图片。 密文&#xff1a; 解题思路&#xff1a; 1、这道题我尝试了很多方法&#xff0c;知道看了别人的wp才知道flag在我忽略的地方。将图片在010 Editor中打开&#xff0c;从…

服务号升级订阅号的流程

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;首先我们要知道服务号和订阅号有什么区别。服务号侧重于对用户进行服务&#xff0c;每月可推送4次&#xff0c;每次最多8篇文章&#xff0c;发送的消息直接显示在好友列表中。订阅号更侧重于信息传…

【QT】绘图设备

绘图设备是指继承QPainterDevice的子类。Qt提供了很多这样的类&#xff0c;例如QPixmap、QBitmap、QImage和 QPicture。其中&#xff0c; QPixmap专门为图像在屏幕上的显示做了优化QBitmap是QPixmap的一个子类&#xff0c;它的色深限定为1&#xff0c;可以使用 QPixmap的isQBi…

使用工具+迅雷解决ESP32配置下载问题

因为一些原因下载git上内容相当缓慢或都根本无法下载所以写了一个工具获取链接并使用迅雷下载。 工具下载&#xff1a;【免费】使用迅雷下载开发板工具资源-CSDN文库

分享77个工作总结PPT,总有一款适合您

分享77个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/1qdoA_Ylbxkmp2Qkh9VDw8A?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 水彩插画风幼儿说课PPT模板 舞龙舞狮文化传承通…

【前端设计】HTML+CSS+JavaScript基本特性

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

小程序使用echarts(超详细教程)

小程序使用echarts第一步就是先引用到小程序里面&#xff0c;可以直接从这里下载 文件很多&#xff0c;我们值下载 ec-canvas 就好&#xff0c;下载完成后&#xff0c;直接放在pages同级目录下 index.js 在我们需要的页面的 js 文件顶部引入 // pages/index/index.js impor…

BUUCTF RSA4

BUUCTF RSA4 下载题目&#xff0c;可见文件给出了3组n和c N 331310324212000030020214312244232222400142410423413104441140203003243002104333214202031202212403400220031202142322434104143104244241214204444443323000244130122022422310201104411044030113302323014101…

从零开始的目标检测和关键点检测(三):训练一个Glue的RTMPose模型

从零开始的目标检测和关键点检测&#xff08;三&#xff09;&#xff1a;训练一个Glue的RTMPose模型 一、重写config文件二、开始训练三、ncnn部署 从零开始的目标检测和关键点检测&#xff08;一&#xff09;&#xff1a;用labelme标注数据集 从零开始的目标检测和关键点检测…

Java自学第1课:安装JDK+Eclipse

1 引言 在学习前&#xff0c;我想说一句&#xff0c;那就是为什么要学习Java。 每个人的出发点都不同&#xff0c;对于做信息化的工程技术人员来说&#xff0c;java不懂&#xff0c;就没法干项目。 尽管有c和matlab等基础&#xff0c;但java看起来与这些语言都不太一样。 做…

PXI-6608 185745H-02 PXI-6527 185633D-01

PXI-6608 185745H-02 PXI-6527 185633D-01 人工智能技术并不新鲜&#xff0c;但运行它的数据和计算却很新鲜 对于那些对人工智能技术历史感兴趣的人来说&#xff0c;一些今天正在使用的技术从20世纪50年代和60年代就已经存在了。 但是&#xff0c;如果人工智能已经存在了这么…

智能电表瞬时电量是什么意思?

智能电表已经成为我们进行能源管理的重要工具。其中&#xff0c;瞬时电量这一概念逐渐走进大众视野。那么&#xff0c;智能电表瞬时电量究竟是什么意思&#xff1f;它对我们的生活和能源管理又有哪些影响呢&#xff1f;下面&#xff0c;小编就来为大家介绍一下瞬时电量&#xf…