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

news2024/11/27 14:29:56

通过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 MainActivity
    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 = {
                    //加密
                    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.coloradmin.cn/o/1169038.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路由器基础(十一):ACL 配置

访问控制列表 (Access Control List,ACL) 是目前使用最多的访问控制实现技术。访问控制列表是路由器接口的指令列表&#xff0c;用来控制端口进出的数据包。ACL适用于所有的被路由协议&#xff0c;如IP、IPX、AppleTalk 等。访问控制列表可以分为基本访问控制列表和高级访问控制…

nodejs+springboot+elementui+python的Sd球鞋销售平台的设计与实现-毕业设计

此网站系统的开发方式和信息管理方式&#xff0c;借鉴前人设计的信息和研发。以网站商品信息为主&#xff0c;购物商品为核心功能来进行设计和研发&#xff0c;把网站信息和技术整合&#xff0c;开发出一套Sd球鞋销售平台。用目前现有的新技术进行系统开发&#xff0c;提供后台…

K-edge 和逃逸问题

一 k-eage基本概念 1 k-edge概念 K-edge称为K边, 其物理意义是高原子序数物质原子内部K层自由电子, 易与特定能量下X射线光子发生光电吸收作用, 导致对该能量的X射线光子吸收特别大。 而K-edge特性表现为X射线与物质发生相互作用时, 其衰减系数随着能量的增加而逐渐减小, 但在…

【优选算法系列】第一节.栈的简介(1047. 删除字符串中的所有相邻重复项和844. 比较含退格的字符串)

文章目录 前言一、删除字符串中的所有相邻重复项和 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、比较含退格的字符串 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 …

Vue组件化开发,组件的创建,注册,使用,详解Vue,vm,VueComponent,vc

组件化开发 模块是指将一个大的js文件按照模块化拆分规则进行拆分成的每个js文件, 凡是采用模块方式开发的应用都可以称为模块化应用(组件包括模块) 传统方式开发的一个网页通常包括三部分: 结构(HTML)、样式(CSS)、交互(JavaScript) 关系纵横交织复杂&#xff0c;牵一发动全…

4+m6A+机器学习+分型,要素过多,没有思路的同学可借鉴

今天给同学们分享一篇生信文章“Diagnostic, clustering, and immune cell infiltration analysis of m6A regulators in patients with sepsis”&#xff0c;这篇文章发表在Sci Rep.期刊上&#xff0c;影响因子为4.6。 结果解读&#xff1a; 脓毒症中m6A调节因子的转录改变 …

ChinaSoft 论坛巡礼 | 安全攸关软件的智能化开发方法论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

5.RDD持久化

概述 今日目标&#xff1a; RDD 持久化 RDD持久化原理RDD持久化策略如何选择RDD持久化策略案例 相关文章如下&#xff1a; spark官网地址RDD编程指南 RDD 持久化 RDD持久化原理 Spark中最重要的功能之一是跨操作在内存中持久化&#xff08;或缓存&#xff09;数据集。当…

Make.com实现多个APP应用的自动化的入门指南

Make.com是一款基于云的自动化平台&#xff0c;可帮助用户将多个应用程序连接在一起&#xff0c;并通过设置自动化流程来简化日常任务。Make.com提供丰富的API集成&#xff0c;支持连接各种流行的应用程序&#xff0c;包括社交媒体、电子商务、CRM等。 使用Make.com实现多个AP…

AM@点与点集的关系@n维空间邻域

文章目录 abstract坐标平面平面点集 平面邻域利用邻域描述点与点集的关系聚点点集分类 n n n维空间基础概念线性运算和空间概念 空间中的两点距离 n n n维空间中的变元极限 n n n维空间内的邻域 abstract 坐标平面和平面点集, n n n维空间点集点与点集的关系n维空间及其邻域 …

python用cv2画图(line, rectangle, text等)

Python做图像图形研究的时候&#xff0c;通常需要画很多辅助几何形状&#xff08;比如bounding box等&#xff09;。基于opencv的几何图形绘制具有易用性&#xff0c;而且天然能和numpy数组交互。 本文总结了几种常用的cv2画几何图形的方法&#xff0c;当一个简易的手册使用&a…

【this详解】学习JavaScript的this的使用和原理这一篇就够了,超详细,建议收藏!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;前端js专栏 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续前进的…

IS200EPSMG1AED 使用Lua创建逻辑脚本或完整程序

IS200EPSMG1AED 使用Lua创建逻辑脚本或完整程序 IS200EPSMG1AED 是一种支持网络的I/O控制器&#xff0c;它执行类似于可编程逻辑控制器(PLC)的控制、逻辑和监控功能。然而&#xff0c;与PLC不同&#xff0c;X-600M是为基于网络的应用而设计的。X-600M可以使用内置网络服务器和…

094基于web+springboot的酒店客房管理系统

欢迎大家关注&#xff0c;一起好好学习&#xff0c;天天向上 文章目录 一项目简介技术介绍 二、功能组成三、效果图四、 文章目录 一项目简介 本酒店客房管理系统有管理员&#xff0c;用户&#xff0c;会员&#xff0c;清洁人员。管理员功能有个人中心&#xff0c;用户管理&am…

2023年最新CUDA安装教程,看完就会(windows版)。

目录 前言 注意&#xff1a;本教程建立在您已经正确安装了显卡驱动的基础上 第一步&#xff0c;找到对应的CUDA版本 第二步&#xff0c;查看你要安装的pytorch版本对应的cuda版本&#xff08;如果只需要安装cuda可以跳过&#xff09; 第二步,下载cuda 第三步&#xff0c;…

PointNet++ 论文阅读

论文链接 PointNet 0. Abstract **背景&#xff1a;**PointNet的设计并未捕捉到度量空间中存在的局部结构&#xff0c;限制了其识别细粒度模式和适用于复杂场景的能力 解决思路&#xff1a; 引入了一种分层神经网络&#xff0c;该网络在输入点集的嵌套分割上递归地应用Poin…

[PHP]禅道项目管理软件ZenTaoPMS源码包 v16.4

禅道项目管理软件ZenTaoPMS一键安装包是一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体&#xff0c;是一款专业的研发项目管理软件&#xff0c;完整地覆盖了项目管理的核心流程。注重实效的管理思想&#xff0c;合理的软件…

C语言,数据结构指针,结构构体操作符 •,->,*的区别,看这篇就够了

在朋友们学习指针和数据结构这一章的时候&#xff0c;对各种操作符云里雾里。当你看到这么文章之后你就会明白了。 一 • 和 ->运算符 • 运算符&#xff1a;是结构变量访问结构体成员时用的操作符 -> 运算符&#xff1a;这是结构体指针访问结构体成员时调用的运算符。 …

GCN火车票识别项目 P1 火车票识别项目介绍 Pytorch LSTM/GCN

从本节开始&#xff0c;我将带大家完成一个深度学习项目&#xff1a;用图卷积神经网络(GCN)&#xff0c;实现一个「火车票文字信息提取」的项目&#xff0c;由于火车票上每个节点文字不是等长的&#xff0c;所以还需要添加一个前置的 LSTM 来提取句子特征。 课前说明 1、这是…

VScode + opencv + c++ + win配置教程

准备&#xff1a; 1、下载opencv 2、下载MinGw 3、 3、下载CMake 下载完解压放到一个文件夹里面&#xff0c;便于环境管理&#xff0c;文件夹我重命名了&#xff0c;解压出来文件名不一样正常 环境变量配置 C:\Users\wuxulong\cpp_env\MinGw\mingw64\bin C:\Users\wuxulon…