当前位置: 首页 > news >正文

Android笔记(二十四)基于Compose组件的MVVM模式和MVI模式的实现

仔细研究了一下MVI(Model-View-Intent)模式,发现它和MVVM模式非常的相识。在采用Android JetPack Compose组件下,MVI模式的实现和MVVM模式的实现非常的类似,都需要借助ViewModel实现业务逻辑和视图数据和状态的传递。在这篇文章中,将通过简单的货币兑换实例来展示一下MVVM模式和MVI模式的不同。

一、MVVM模式

MVVM
图1 MVVM模式架构
在MVVM模式中:
M:表示Model,即数据域模型。数据模型中的数据通过视图模型传递给视图,从而更新视图的界面;
V: 表示View,即视图,可以看到的界面;从视图界面中将输入的数据发送给视图模型,视图模型执行业务逻辑,完成某些业务功能。
VM:表示ViewModel,即视图模型,是业务的实际的处理者。它承担着中介的作用,它一方面将数据模型传递给界面,使得界面发生刷新;另一方面,将视图中的数据传递发送给Model数据模型,为后续的业务处理提供数据。在MVVM模式中是双向的数据绑定的。在Android Compose组件定义界面的过程中,往往是数据单向流动的。因此在结合Compose组件实现MVVM模式时,处理与DataBinding组件实现双向绑定是有些不同的。下面通过中美货币兑换应用实例来说明:
1.1 定义数据模型

/*** @property type String:货币转换类别,例如RMB->USD,或USD->RMB* @property moneny Float:要转换的钱数* @property rate Float:转换汇率* @constructor*/
data class Currency(var type:String="RMB->USD",var money:Float=0.0f,var rate:Float=0.14f)

1.2 定义视图模型CurrencyViewModel.kt
CurrencyViewModel类是ViewModel的子类,定义核心业务,即修改界面的状态数据和兑换货币业务处理,代码如下:

class CurrencyViewModel: ViewModel() {private var currency = Currency()//要处理的数据private var _result: MutableStateFlow<String> =  MutableStateFlow("")//视图模型内部调用val result:StateFlow<String> = _result.asStateFlow()//单向数据流提供给视图fun updateUI(type:String,money:Float){//修改数据if(type == "USD->RMB")currency.rate = 7.07felse if(type == "RMB->USD")currency.rate = 0.14fcurrency.type = typecurrency.money = money}fun convert() {//兑换货币_result.value  ="${currency.money*currency.rate}"}
}

1.3 定义视图CurrencyScreen
CurrencyScreen是可组合函数,由多个可组合项构成,代码如下:

@Composable
fun CurrencyScreen(modifier: Modifier, viewModel:CurrencyViewModel) {val expandState = remember{ mutableStateOf(false) }//控制下拉列表的状态val types = listOf("","CNY->USD","USD->RMB")//定义货币兑换的所有类别var type by remember{ mutableStateOf("") }  //定义兑换类别var money by remember { mutableFloatStateOf(0.0f) }//定义要兑换的钱数val result = viewModel.result.collectAsState()//获取结果状态Column(modifier=modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally){Text("货币兑换简单应用",fontSize = 32.sp)//输入钱数的文本框OutlinedTextField(modifier = Modifier.size(300.dp,60.dp),label = {//提示Text("要兑换的钱数:")},value = "${money}",onValueChange = {it:String->money = it.toFloat()})//自定义下拉列表,控制类别Row(modifier = Modifier.size(300.dp,60.dp)){OutlinedTextField(value = "$type", onValueChange = {}, readOnly = true)//不可编辑DropdownMenu(expanded = expandState.value ,onDismissRequest = {expandState.value = false}) {types.forEach {it:String->DropdownMenuItem(text = {Text(it)}, onClick = {type = it           //修改兑换类别expandState.value = false//关闭下拉列表框})}}IconButton(onClick={expandState.value = !expandState.valueviewModel.updateUI(type,money)}) {Icon(Icons.Filled.PlayArrow, contentDescription = "下拉图标")}}//兑换按钮Button(onClick={viewModel.convert()                   //执行货币转换}){Text("货币转换")}//显示结果if(type.isNotBlank())Text("$type${result.value}",fontSize = 30.sp)}
}

1.4 定义MainActivity
MainActivity调用上述的界面可组合函数CurrencyScreen和创建视图模型CurrencyViewModel对象,代码如下:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()val viewModel = ViewModelProvider(this).get(CurrencyViewModel::class.java)//创建视图模型对象setContent {Ch03_DemoTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->CurrencyScreen(modifier = Modifier.padding(innerPadding),viewModel =viewModel )//调用可组合函数,生成界面}}}}
}

运行结果如图2所示:
在这里插入图片描述
图2

二、MVI模式

在这里插入图片描述
图3 MVI模式架构
Model: 与MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态。
View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Model的变化实现界面刷新。
Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求;
下面仍以货币兑换为例进行介绍。

2.1 定义模型

/*** @property operator String:操作的类别* @property rmb Double:人民币* @property rate Double:汇率* @property usd Double:美元* @constructor*/
data class CurrencyState(var operator:String="None",var rmb:Double=0.0,val rate:Double=1.0,var usd:Double=0.0)

2.2 定义意图

在本应用中有两个意图,刷新输入界面和兑换货币,因此定义密封类CurrencyIntent,两个子类ConvertToRMBIntent和ConvertToUSDIntent,分别对应兑换成人民币操作和兑换成美元的操作,并通过意图传递参数,代码如下:

sealed class CurrencyIntent {data class ConvertToRMBIntent(val operator:String,val usd:Double,val rate:Double):CurrencyIntent()data class ConvertToUSDIntent(val operator:String,val rmb:Double,val rate:Double):CurrencyIntent()
}

2.3 定义视图模型

class CurrencyViewModel: ViewModel() {private val _state = MutableStateFlow(CurrencyState())val output = _state.asStateFlow()fun processIntents(intent: CurrencyIntent){//根据意图类型的不同处理意图val currentState = _state.valuewhen(intent){is CurrencyIntent.ConvertToRMBIntent->{val newState = currentState.copy(operator=intent.operator,usd =intent.usd,rate = intent.rate)newState.rmb = convertToRMB(newState.usd,newState.rate)//执行兑换_state.value = newState//修改状态}is CurrencyIntent.ConvertToUSDIntent->{val newState = currentState.copy(operator=intent.operator,rmb=intent.rmb,rate = intent.rate)newState.usd = convertToUSD(newState.rmb,newState.rate)//执行兑换_state.value = newState//修改状态}}}private fun convertToUSD(rmb:Double,rate:Double):Double = rmb*rateprivate fun convertToRMB(usd:Double,rate:Double):Double = usd*rate
}

2.4 定义视图

在视图部分,将处理输入的界面单独定义成CurrencyView,代码如下:

@Composable
fun CurrencyView(modifier:Modifier,onReceivedIntent:(CurrencyIntent)->Unit){var expand by remember{mutableStateOf(false)}var inputState by remember{mutableStateOf(0.0)}var operator by remember{mutableStateOf("None")}val operators = listOf("None","CNY->USD","USD->CNY")Column(modifier = modifier.fillMaxWidth().wrapContentSize().padding(10.dp)){Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement =  Arrangement.Center){Row{OutlinedTextField(value="$inputState",onValueChange = {it:String->inputState = it.toDouble()},label = {Text("输入货币",fontSize=20.sp)})}Row{OutlinedTextField(value = "$operator", onValueChange = {})IconButton(onClick={expand = !expand}){Icon(Icons.Filled.ArrowDropDown, contentDescription = "下拉按钮",tint= Color.Green)}DropdownMenu(expanded = expand, onDismissRequest = {expand = false}) {operators.forEach {it:String->DropdownMenuItem(text = {Text(it,fontSize = 24.sp)}, onClick = {if(it=="USD->CNY"){operator ="USD->CNY"onReceivedIntent(CurrencyIntent.ConvertToRMBIntent("USD->CNY",inputState,7.1))expand = false}else if(it=="CNY->USD"){operator = "CNY->USD"onReceivedIntent(CurrencyIntent.ConvertToUSDIntent("CNY->USD",inputState,0.14))expand = false}})}}}}}
}

然后将输入数据的界面CurrencyView在CurrencyScreen调用,并在CurrencyScreen增加兑换的输出结果显示,代码如下:

@Composable
fun CurrencyScreen(modifier:Modifier,viewModel:CurrencyViewModel){val state = viewModel.output.collectAsState()Column(modifier = modifier){CurrencyView(modifier){viewModel.processIntents(it) //处理意图}//显示兑换结果if(state.value.operator=="USD->CNY") Text("$ ${state.value.usd} 美元=¥ ${state.value.rmb} 人民币",fontSize=24.sp)else if(state.value.operator=="CNY->USD")Text("¥ ${state.value.rmb} 人民币=$ ${state.value.usd} 美元",fontSize=24.sp)}
}

2.5 定义MainActivity

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()val viewModel: CurrencyViewModel = ViewModelProvider(this).get(CurrencyViewModel::class.java)setContent {Ch03_DemoTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->CurrencyScreen(modifier=Modifier.padding(innerPadding),viewModel = viewModel)}}}}
}

运行结果如图4所示:
在这里插入图片描述

图4

参考文献

Android应用架构的未来:深入理解MVI模式及其优势 https://cloud.tencent.com/developer/article/2394218

相关文章:

Android笔记(二十四)基于Compose组件的MVVM模式和MVI模式的实现

仔细研究了一下MVI(Model-View-Intent)模式&#xff0c;发现它和MVVM模式非常的相识。在采用Android JetPack Compose组件下&#xff0c;MVI模式的实现和MVVM模式的实现非常的类似&#xff0c;都需要借助ViewModel实现业务逻辑和视图数据和状态的传递。在这篇文章中&#xff0c…...

MySQL 是否支持 XML

MySQL 是否支持 XML&#xff1a;概述与应用 虽然 MySQL 主要以处理关系型数据为主&#xff0c;但它也提供了对 XML 数据的支持。XML&#xff08;可扩展标记语言&#xff09;是一种用于数据传输和存储的通用格式。在许多应用场景中&#xff0c;XML 被广泛用于数据交换、配置文件…...

pikachu靶场总结(四)

九、越权漏洞 1.概述 如果使用A用户的权限去操作B用户的数据&#xff0c;A的权限小于B的权限&#xff0c;如果能够成功操作&#xff0c;则称之为越权操作。 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。 一般越权漏洞容易出现在权限页面&#xff08;需要登…...

24.3 基于文件的服务发现模式

本节重点介绍 : 基于文件的服务发现提供了一种配置静态目标的更通用的方法可以摆脱对特定服务发现源的依赖通常的做法是调用内部CMDB的接口获取target数据&#xff0c;打上标签&#xff0c;生成json文件发给prometheus采集 基于文件的服务发现模式 解决的问题 之前手动配置…...

【Java】面向UDP接口的网络编程

【Java】面向UDP接口的网络编程 一. 基本通信模型二. APIDatagramSocketDatagramPacket 三. 回显服务器/客户端示例服务器客户端总结 一. 基本通信模型 UDP协议是面向数据报的&#xff0c;因此此处要构建数据报(Datagram)在进行发送。 二. API DatagramSocket DatagramSocke…...

SRS服务器搭建

1、配置 listen 1935; max_connections 1000; #srs_log_tank file; #srs_log_file ./objs/srs.log; daemon on; http_api { enabled on; listen 1985; } http_server { enabled on; listen 808…...

iMazing只能苹果电脑吗 Win和Mac上的iMazing功能有区别吗

在当今数字时代&#xff0c;管理和备份手机数据变得越来越重要。无论是转移照片、备份短信&#xff0c;还是管理应用程序&#xff0c;一个强大的工具可以大大简化这些操作。iMazing作为一款备受好评的iOS设备管理软件&#xff0c;已经成为许多用户的选择。但是&#xff0c;许多…...

ChatGPT可以分析股票吗?

结合国庆前大A股市的小波牛市以及今天的股市表现&#xff0c;我从多个角度为你提供一些分析和建议&#xff1a; 一、国庆前的小波牛市分析 国庆前&#xff0c;大A股市出现了一波小幅上涨&#xff0c;市场呈现出一些积极的信号&#xff1a; 政策面利好&#xff1a;政府出台了…...

Dockerfile搭建镜像

Dockerfile搭建镜像的优势与区别 引言 在现代软件开发与运维中&#xff0c;容器化技术日益普及&#xff0c;而Docker作为最流行的容器化平台之一&#xff0c;通过Dockerfile提供了一种灵活、自动化的方式来构建Docker镜像。Dockerfile使得镜像的构建过程可重复、可版本化&…...

Kubernetes-Kind篇-01-kind搭建测试集群

1、Kind 介绍 官方文档地址&#xff1a;https://kind.sigs.k8s.io/ github仓库地址&#xff1a;https://github.com/kubernetes-sigs/kind 国内镜像仓库地址&#xff1a;https://gitcode.com/gh_mirrors/ki/kind/overview kind 是一种使用 Docker 容器 nodes 运行本地 Kubern…...

在UniApp中高效处理大量文件请求的策略

在开发跨平台应用时&#xff0c;尤其是在使用UniApp这样的框架时&#xff0c;我们可能会遇到需要同时请求多个文件的情况。然而&#xff0c;不加节制地同时发起大量请求可能会带来严重的性能问题&#xff0c;如界面卡顿、内存溢出、网络带宽饱和等。本文将探讨如何在UniApp中高…...

docker compose入门4—常用命令

在使用 Docker Compose 管理多容器应用时&#xff0c;常见的命令帮助我们高效地管理容器的生命周期、服务、日志等。以下是一些常用的 Docker Compose 命令及其详细讲解&#xff1a; 1. docker-compose up 这个命令用于启动定义在 docker-compose.yml 文件中的服务。 用法&am…...

wps文本框文字居中对齐

直接点对齐里的水平居中&#xff0c;垂直居中是将文本框水平垂直居中&#xff0c;文字不会居中 将文本框里的文字居中&#xff1a; 垂直居中&#xff1a; 水平居中&#xff1a;...

注册信息页面

知识点&#xff1a; &#xff01;&#xff0b;Enter 直接生成前端基本框架 1.<h1></h1> (2,3,4,5) 表示各级标题 2.<form></form> 表单建立 3.<input type" "></input> 表格&#xff08;表单嵌套表格&#xff09; type属…...

详解Java中的BIO、NIO、AIO

1、 详解Java中的BIO、AIO、NIO 1.1、引言 IO流是Java中比较难理解的一个知识点&#xff0c;但是IO流在实际的开发场景中经常会使用到&#xff0c;比如Dubbo底层就是NIO进行通讯。本文将介绍Java发展过程中出现的三种IO&#xff1a;BIO、NIO以及AIO&#xff0c;重点介绍NIO。…...

CAN和CANFD如何转换和通信

随着科技的发展&#xff0c;汽车电子和工业领域中CAN通信需要承载数据量也越来越大&#xff0c;传统CAN通信有了向CANFD通信过渡的倾向。在实现过渡的过程中可能会出现自己设备是CAN通信&#xff0c;客户设备是CANFD通信的情况&#xff0c;或者自己设备是CANFD通信&#xff0c;…...

QDateTimeEdit Class

Header:#include qmake:QT += widgets Inherits:QAbstractSpinBox Inherited By:QDateEdit and QTimeEdit Public Types enum Section {NoSection, AmPmSection, MSecSection, SecondSection, MinuteSection, …, YearSection } flags SectionsProperties calendarPopu…...

Windows环境安装CentOS7

【注意】安装CentOS需要先安装Vmware虚拟机 【下载前准备】 一、下载CentOS 7镜像文件阿里云镜像开源&#xff0c;点击跳转 二、安装VMware&#xff08;17&#xff09;&#xff1a; a. 官网&#xff0c;点击跳转 b. 许可证&#xff1a;JU090-6039P-08409-8J0QH-2YR7F 安装V…...

用docker启动mysql步骤

以下是在 Docker 中启动 MySQL 的详细步骤&#xff1a; **一、拉取 MySQL 镜像 ** 1. 打开终端&#xff0c;确保 Docker 服务正在运行。可以使用以下命令检查 Docker 服务状态&#xff1a; sudo systemctl status docker 2. 使用以下命令拉取 MySQL 官方镜像&#xff1a; d…...

[Linux] Linux 初识进程地址空间 (进程地址空间第一弹)

标题&#xff1a;[Linux] Linux初识进程地址空间 个人主页水墨不写bug &#xff08;图片来源于AI&#xff09; 目录 一、什么是进程地址空间 二、为什么父子进程相同地址的变量的值不同 三、初识虚拟地址、页表 一、什么是进程地址空间 其实&#xff0c;在很久之前&#xf…...

力扣21~25题

21题&#xff08;简单&#xff09;&#xff1a; 分析&#xff1a; 按要求照做就好了&#xff0c;这种链表基本操作适合用c写&#xff0c;python用起来真的很奇怪 python代码&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, v…...

04. prometheus 监控 Windows 服务器

prometheus 监控 Windows 服务器 1. 下载安装 Windows_exporter 安装包下载&#xff1a;https://github.com/prometheus-community/windows_exporter/releases 下载 msi 版本&#xff0c;上传至要监控的 Windows 服务器&#xff0c;双击安装即可&#xff0c;exporter 会自动…...

【机器学习】——决策树以及随机森林

文章目录 1. 决策树的基本概念与结构1.1 决策树的构建过程 2. 决策树的划分标准2.1 信息增益&#xff08;Information Gain&#xff09;2.2 信息增益比&#xff08;Information Gain Ratio&#xff09;2.3 基尼指数&#xff08;Gini Index&#xff09;2.4 均方误差&#xff08;…...

怎么选择合适的数据恢复软件?适用于 Windows 的数据恢复软件对比

针对 Windows 的领先数据恢复软件的全面回顾&#xff1a; 丢失重要数据对任何 Windows 用户来说都是一场噩梦。从意外删除到系统崩溃&#xff0c;数据丢失是一个非常普遍的问题。值得庆幸的是&#xff0c;有强大的数据恢复工具可以帮助找回丢失的文件。这篇评论深入探讨了适用于…...

CI/CD 和 DevOps 工具概述:Jenkins 、Docker 的概述、工作流程、对比

随着软件开发的复杂性不断增加&#xff0c;持续集成&#xff08;CI&#xff09;、持续交付&#xff08;CD&#xff09;和运维&#xff08;Ops&#xff09;的概念逐渐成为现代软件开发流程中的核心组成部分。这些概念促进了开发团队与运维团队之间的协作&#xff0c;提升了软件的…...

基于SpringBoot+Vue+uniapp的高校教务管理小程序系统设计和实现

2. 详细视频演示 文章底部名片&#xff0c;联系我获取更详细的演示视频 3. 论文参考 4. 项目运行截图 代码运行&#xff0c;效果展示图 代码运行&#xff0c;效果展示图 代码运行&#xff0c;效果展示图 代码运行&#xff0c;效果展示图 代码运行&#xff0c;效果展示图 5. 技…...

如何在 Ubuntu VPS 上从 Apache Web 服务器迁移到 Nginx

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 在启动网站或应用程序时&#xff0c;您需要做出许多选择。有时&#xff0c;您的需求会发生变化&#xff0c;新技术变得可行&#x…...

pikachu靶场总结(一)

最近看到好多人还在打这个靶机所以把以前写的总结放上来了&#xff0c;主要是皮卡丘靶场漏洞的原理&#xff0c;利用方式&#xff0c;防护方法简略总结&#xff0c;纯手敲记录&#xff0c;总结如果不到位请评论区留言&#xff01; 一、暴力破解 1.基于表单的暴力破解 原理&a…...

No.2 笔记 | 网络安全攻防:PC、CS工具与移动应用分析

引言 在当今数字化时代,网络安全已成为每个人都应该关注的重要话题。本文将总结一次关于网络安全攻防技术的学习内容,涵盖PC端和移动端的恶意程序利用,以及强大的渗透测试工具Cobalt Strike的使用。通过学习这些内容,我们不仅能够了解攻击者的手法,更能提高自身的安全意识和防…...

QD1-P8 HTML格式化标签

本节学习&#xff1a;HTML 格式化标签。 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p8 ‍ 一、font 标签 用途&#xff1a;定义文本的字体大小、颜色和 face&#xff08;字体类型&#xff09;。 示例 <!DOCTYPE html> <html><head><meta cha…...