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.07f
       else if(type == "RMB->USD")
           currency.rate = 0.14f
       currency.type = type
       currency.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.value
               viewModel.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.value
        when(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*rate
    private 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

易我数据恢复软件,一键找回你的重要资料!

我们生活在数字时代&#xff0c;数据对我们来说超级重要。工作文件、学习资料&#xff0c;还有照片视频&#xff0c;这些东西要是没了或者不小心删了&#xff0c;那得多烦人啊。幸好现在科技发达&#xff0c;有了数据恢复软件&#xff0c;就像给我们数据上了一把安全锁。市面上…

一篇闪击常用放大器电路(学习笔记)

文章目录 声明概念名词经典电路分析反向放大器同向放大器加法器减法器积分电路微分电路差分放大电路电流->电压转换电路电压->电流转换电路 虚短与虚断一、虚短二、虚断 一些碎碎念 声明 ​ 本文是主要基于以下两篇博客所做的笔记&#xff1a; 模电四&#xff1a;基本放…

IT招聘乱象的全面分析

近年来&#xff0c;IT行业的招聘要求似乎越来越苛刻&#xff0c;甚至有些不切实际。许多企业在招聘时&#xff0c;不仅要求前端工程师具备UI设计能力&#xff0c;还希望后端工程师精通K8S服务器运维&#xff0c;更有甚至希望研发经理掌握所有前后端框架和最新开发技术。这种招聘…

MySQL基本语法、高级语法知识总结以及常用语法案例

MySQL基本语法总结 MySQL是一种广泛使用的关系型数据库管理系统&#xff0c;其基本语法涵盖了数据库和数据表的创建、查询、修改和删除等操作。 一、数据库操作 创建数据库&#xff08;CREATE DATABASE&#xff09; 语法&#xff1a;CREATE DATABASE [IF NOT EXISTS] databa…

最新PHP礼品卡回收商城 点卡回收系统源码_附教程

最新PHP礼品卡回收商城 点卡回收系统源码_附教程 各大电商平台优惠券秒杀拼团限时折扣回收商城带余额宝 1、余额宝理财 2、回收、提现、充值、新订单语音消息提醒功能 3、带在线客服 4、优惠券回收功能 源码下载&#xff1a;https://download.csdn.net/download/m0_66047…

Android实现App内直接预览本地PDF文件

在App内实现直接预览pdf文件&#xff0c;而不是通过调用第三方软件&#xff0c;如WPS office等打开pdf。 主要思路&#xff1a;通过PhotoView将pdf读取为图片流进行展示。 一、首先&#xff0c;获取对本地文件读取的权限 在AndrooidManifest.xml中声明权限&#xff0c;以及页…

Windows,MySQL主从复制搭建

前提&#xff1a;windows环境&#xff0c;同一个服务器安装多个相同版本的mysql数据库 多个MySQL服务搭建完成后&#xff0c;下面我们进行主从复制的相关配置 1.主数据库 执行指令 #创建用户 CREATE USER slavelocalhost IDENTIFIED BY 123456;#授权 GRANT REPLICATION SLA…

[CS报错]找不到 .NETFramework,Version=v4.6.2 的引用程序集

报错 error MSB3644: 找不到 .NETFramework,Versionv4.6.2 的引用程序集。要解决此问题&#xff0c;请为此框架版本安装开发人员工具包(SDK/目标包)或者重新定向应用程序。可在 https://aka.ms/msbuild/developerpacks 处下载 .NET Framework 开发人员工具包 解决 打开https…

【计网】从零开始理解UDP协议 --- 理解端口号和UDP结构

我依旧敢和生活顶撞&#xff0c; 敢在逆境里撒野&#xff0c; 直面生活的污水&#xff0c; 永远乐意为新一轮的月亮和日落欢呼。 --- 央视文案 --- 从零开始理解UDP协议 1 再谈端口号2 理解UDP 报头结构3 UDP 的特点4 UDP 的缓冲区5 UDP 使用注意事项 1 再谈端口号 之前我…

pycharm里debug时如何看到数据的维度

使用表达式计算&#xff08;Evaluate Expression&#xff09; 调试时&#xff0c;使用 PyCharm 的 “Evaluate Expression” 功能可以动态查看或修改数据。具体步骤如下&#xff1a; 在调试模式中按 Alt F8&#xff08;Windows&#xff09;或 Option F8&#xff08;Mac&…

Cortex-A7:如何切换ARM和Thumb状态

0 参考资料 ARM Cortex-A(armV7)编程手册V4.0.pdf1 Cortex-A7&#xff1a;如何切换ARM和Thumb状态 1.1 Cortex-A7支持的指令集 Cortex-A7支持的指令集包括ARM指令集和Thumb-2&#xff08;ARM官方一般用Thumb表示&#xff09;指令集。 ARM指令集指令大小都是32位&#xff0c;…

2024 年 04 月编程语言排行榜,PHP 排名创新低?

编程语言的流行度总是变化莫测&#xff0c;每个月的排行榜都揭示着新的趋势。2024年4月的编程语言排行榜揭示了一个引人关注的现象&#xff1a;PHP的排名再次下滑&#xff0c;创下了历史新低。这种变化对于PHP开发者和整个技术社区来说&#xff0c;意味着什么呢&#xff1f; P…

第十一章 RabbitMQ之消费者确认机制

目录 一、介绍 二、演示三种ACK方式效果 2.1. none: 不处理 2.1.1. 消费者配置代码 2.1.2. 生产者主要代码 2.1.3. 消费者主要代码 2.1.4. 运行效果 2.2. manual&#xff1a;手动模式 2.3. auto&#xff1a;自动模式 一、介绍 消费者确认机制&#xff08;Consume…

【计算机网络】详谈TCP协议确认应答机制捎带应答机制超时重传机制连接管理机制流量管理机制滑动窗口拥塞控制延迟应答

一、TCP 协议段格式 1.1、4位首部长度 4位首部长度的基本单位是4字节&#xff0c;也就是说如果4位首部长度填6&#xff0c;那报头长度就是24字节。报头长度的取值范围为[0,60]字节&#xff0c;也就是说选项的最大长度为40字节。 二、确认应答机制 发送数据和发送应答&#x…

CSS 入门

1. CSS 1.1 概念 CSS&#xff08;Cascading Style Sheet&#xff09;&#xff0c;层叠样式表&#xff0c;用于控制页面的样式 CSS 能够对网页中元素位置的排版进行像素级精确控制&#xff0c;实现美化页面的效果&#xff0c;能够做到页面的样式和结构分离&#xff08;类似于…

Redis总结(官方文档解读)

定义 Redis是一个开源的&#xff0c;基于内存的数据结构存储系统&#xff0c;可以用作数据库、缓存和消息中间件。 特征 高性能 支持丰富的数据类型 丰富的操作类型&#xff0c;操作是原子性的 支持周期性持久化 支持分布式 开源免费&#xff0c;社区活跃 数据类型 数据…

基础篇:带你打开Vue的大门(一)

学习目标&#xff1a; 理解Vue的基本概念&#xff1a;掌握Vue.js是什么&#xff0c;它的设计理念&#xff0c;以及它在现代Web开发中的应用。掌握Vue的基本语法&#xff1a;学习Vue的基础指令和语法&#xff0c;能够使用Vue构建简单的交互式界面。熟悉Vue组件化开发&#xff1…

vue3--通用 popover 气泡卡片组件实现

背景 在日常开发中,我们一般都是利用一些诸如:element-ui、element-plus、ant-design等组件库去做我们的页面或者系统 这些对于一些后台管理系统来说是最好的选择,因为后台管理系统其实都是大同小异的,包括功能、布局结构等 但是对于前台项目,比如官网、门户网站这些 …

【银行科技岗】相关考试知识点总结及部分考题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、**网络与安全**二、**软件开发与设计**三、**数据库与数据管理**四、**编程与系统**五、**计算机硬件与性能**六、**大数据与人工智能**七、**系统与应用**相关…

人型机器人技术及前景详解

随着科技的飞速发展&#xff0c;人型机器人作为人工智能领域的一个重要分支&#xff0c;正逐步走进我们的生活和工作之中。它们不仅在外形上模拟人类&#xff0c;更在感知、决策、行为和交互能力上展现出强大潜力。本文将深入探讨人型机器人的技术原理、当前发展状况以及未来的…