为Android构建现代应用—— 练习状态管理

news2024/11/23 15:41:26

介绍

本章是一个应用上一章:设计原则中学到的概念的项目。

项目的目标包括以下实现:

        • 创建一个应用程序,该应用程序使用View作为真实来源。

        • 修改应用程序,使其使用ViewModel作为真实来源。

        • 将状态和事件进行分组,以简化View和ViewModel之间的消息传递。

例如,在这个项目中,我们将实现电子商务的一部分屏幕。

该电子商务将在下章:OrderNow,一个真实应用程序中设计和开发的一个应用程序示例。

该屏幕将是OrderScreen,其中包含用户请求的订单信息和用户或买家的其他联系详情。

我们将在项目中只实现屏幕的一部分以简化。目标是练习管理状态的不同方式。

后面会有一个完整的案例:

https://github.com/yaircarreno/building-modern-apps-for-android-code/tree/main/chapter_02

我们的目标是实现一个包含两个字段的表单:

         • User name

        • Phone number

另外,"PayOrder"按钮的启用或禁用将取决于“User name”和“Phone number”字段的正确验证。以下将展示不同的实现选项。

"Views"作为真实源

首先,我们需要识别哪些UI元素可能会发生变化,并在屏幕上代表不同的状态。在这个例子中,它们是:

• 为用户名输入的文本值。

• 为电话号码输入的文本值。

• 支付订单按钮的启用/禁用属性。

因此,在View(Composables)中,我们可以这样表示这些属性:

var name  by remember { mutableStateOf("")}
var phone by remember { mutableStateOf("")}

那么,“Pay order” 按钮的((enable/disable)属性的状态呢?

在这种情况下,这个状态是由其他两个状态:姓名和电话号码衍生出来的。因此,这个状态不需要进一步定义。

View代码可能像这样:

@Preview
@Composable
fun OrderScreen1(){
    var name by remember{ mutableStateOf("")}
    var phone by remember { mutableStateOf("")}

    ContactInformation3(name=name, onNameChange = {name=it},phone=phone, onPhoneChange = {phone=it})
}

@Composable
fun ContactInformation3(name:String,onNameChange:(String)->Unit,phone:String,onPhoneChange:(String) ->Unit) {
    Column(modifier= Modifier
        .fillMaxSize()
        .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally,){

        TextField(
            label={ Text( "User name") },
            value = name,
            onValueChange = onNameChange
        )
        Spacer(modifier = Modifier.padding(5.dp))
        TextField(
            label = { Text(text = "Phone number")},
            value = phone,
            onValueChange=onPhoneChange
        )
        Spacer(Modifier.padding(5.dp))
        Button(onClick = { println("Order generated for $name and phone $phone") }, enabled = name.length>1 && phone.length==11){
            Text(text = "Pay Order")
        }


    }
}

 那么事件呢?

在这个屏幕示例中,我们识别出的事件有:

        • “User name”被修改的事件。

        • “Phone number”被修改的事件。

        • 选中(点击)“Pay Order”按钮的事件。 这些事件的处理方式如下:

	//User name changed
	onNameChange = { name = it }

	...

	//Phone number changed
	onPhoneChange = { phone = it }

	//Pay order clicked
	Button(
	    onClick = {
	        println("Order generated for $name and phone $phone")
	    },
	...
	)

注意!

使用ViewModel,项目中必须有Google在ViewModel中所提供的依赖项,声明依赖项:

dependencies {
	def lifecycle_version = "2.5.0-rc01"

	// ViewModel
	implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

	// ViewModel utilities for Compose
	implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
}

View的完整实现如下:

class OrderViewModel:ViewModel() {
    var name by  mutableStateOf("")
    var onNameChange :(String)->Unit ={ name=it}
    var phone by mutableStateOf("")
    var onPhoneChange:(String)->Unit ={phone=it}
    var payOrder:()->Unit={
        println("name=$name  phone=$phone")
    }
}



@Preview
@Composable
fun OrderScreen(viewModel2: OrderViewModel = viewModel()) {
    ContactInformation4(viewModel2.name,viewModel2.onNameChange,viewModel2.phone,viewModel2.onPhoneChange,viewModel2.payOrder)
}

@Composable
fun ContactInformation4(name: String, onNameChange: (String) -> Unit, phone:String, onPhoneChange: (String) -> Unit, payOrder:()->Unit) {
    Column(modifier = Modifier
        .fillMaxSize()
        .padding(8.dp),horizontalAlignment=Alignment.CenterHorizontally){
        TextField(label = { Text(text = "User name")}, value = name, onValueChange = onNameChange )
        Spacer(modifier = Modifier.padding(5.dp))
        TextField(label = { Text(text = "phone number")},value=phone, onValueChange = onPhoneChange)
        Spacer(modifier = Modifier.padding(5.dp))
        Button(onClick = payOrder, enabled = name.length>1&& phone.length==11) {
            Text(text = "Pay order")
        }

    }
}

在这第二种实现选项中,我们看到状态和事件都被委托给了ViewModel;因此,ViewModel成为了真实源。

随着真实源的改变,设计获得了在ViewModel中应用集中式业务或表示逻辑的灵活性。 到目前为止,我们有了一个合适且工作正常的实现。但是,通过定义组件UI的状态,我们可以看到它可以得到进一步的改善。

分组“States”

在上面的例子中,你可以看到字段是表单的一部分。按钮的状态甚至依赖于表单的字段。因此,将这些UI元素组合成一个包含它们的单一UI元素是有意义的。 由于例子中只有三个UI元素,分组它们的好处可能不那么明显;然而,让我们想象一个屏幕,它有许多其他区域,包含许多其他的UI元素。 首先,将状态组合在一个名为FormUiState的结构中,如下所示:

data class FormUiStates(val name:String="",var phone:String="")

val FormUiStates.successValidated:Boolean get() = name.length>1 && phone.length==11

在ViewModel中,我们用一个单一的状态替换状态,如下所示:

data class FormUiStates(val name:String="",var phone:String="")

val FormUiStates.successValidated:Boolean get() = name.length>1 && phone.length==11




class OrderViewModel5:ViewModel() {
    //UI's states
    var formUiState by mutableStateOf(FormUiStates())
    private set
    //UI's Events
    fun onNameChange():(String)->Unit={
        formUiState=formUiState.copy(name=it)
    }

    fun onPhoneChange():(String)->Unit={
        formUiState= formUiState.copy(phone = it)
    }

    fun payOrder():() ->Unit={
        println("Order generated for ${formUiState.name} and phone ${formUiState.phone}")
    }
    
}


//在View中,我们按照如下方式更新状态的使用:
@Preview
@Composable
fun OrderScreen(viewModel5:OrderViewModel5= viewModel()){
    ContactInformation5(
        name = viewModel5.formUiState.name,
        onNameChange = viewModel5.onNameChange(),
        phone = viewModel5.formUiState.phone,
        onPhoneChange = viewModel5.onPhoneChange(),
        payOrder = viewModel5.payOrder(),
        isValidPayOrder = viewModel5.formUiState.successValidated)
}

@Composable
fun ContactInformation5(name: String,onNameChange: (String) -> Unit,phone: String,onPhoneChange: (String) -> Unit,payOrder: () -> Unit,isValidPayOrder:Boolean) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally
    ){
        TextField(label = { Text(text = "user name")}, value =name , onValueChange =onNameChange )
        Spacer(modifier = Modifier.padding(5.dp))
        TextField(label = { Text(text = "phone number")}, value =phone   , onValueChange =onPhoneChange )
        Spacer(modifier = Modifier.padding(5.dp))
        Button(onClick = payOrder, enabled = isValidPayOrder) {
            Text(text = "Pay Order")
        }
    }
}


当一个屏幕上有许多UI元素时,将相关的UI元素分组变得更加重要。将UI元素分组到组件UI的状态中可以简化、组织和生成在实现中更清晰的代码。 同样的技巧可以应用于事件。如下所示,主要的区别在于表示类型。

分组“Eents”

为了进一步整理代码,我们现在将分组表单的相关事件。首先,我们要创建一个结构来按以下方式分组事件:

我们注意到,事件的分组与上一章中的屏幕UI状态部分解释的技术类似。记住,这是适用的,因为我们定义的不同类型的事件是相关的,但它们可以是互斥的和独立的。

在ViewModel中,消息被简化为如下所示的一个:


@Preview
@Composable
fun OrderScreen7(viewModel7:OrderViewModel7= viewModel()){
    ContactInformation7(
        name = viewModel7.formUiState.name,
        onNameChange = {viewModel7.onFormEvent(FormUiEvent.onNameChange(it))},
        phone = viewModel7.formUiState.phone,
        onPhoneChange = {viewModel7.onFormEvent(FormUiEvent.onPhoneChange(it))},
        payOrder = {viewModel7.onFormEvent(FormUiEvent.payOrderClicked)},
        isValidPayOrder = viewModel7.formUiState.successValidated
    )
}

@Composable
fun ContactInformation7(
    name: String,
    onNameChange: (String) -> Unit,
    phone:String,
    onPhoneChange:(String)->Unit,
    payOrder:()->Unit,
    isValidPayOrder:Boolean) {
    Column(modifier = Modifier
        .fillMaxSize()
        .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally){
        TextField(label = { Text(text = "User name")}, value =name , onValueChange =onNameChange )
        Spacer(modifier = Modifier.padding(5.dp))
        TextField(value = phone, label = { Text(text = "phone number")}, onValueChange =onPhoneChange )
        Spacer(modifier = Modifier.padding(5.dp))
        Button(onClick = payOrder,
            enabled = isValidPayOrder) {
            Text(text = "Play Order")
        }
    }

}




class OrderViewModel7 :ViewModel() {
    //UI's states
    var formUiState by mutableStateOf(FormUiSatate6())
    private set
    //UI's Events
    fun onFormEvent(formEvent:FormUiEvent){
        when(formEvent){
            is FormUiEvent.onNameChange->{
                formUiState=formUiState.copy(name = formEvent.name)
            }
            is FormUiEvent.onPhoneChange->{
                formUiState=formUiState.copy(phone = formEvent.phone)
            }
            is FormUiEvent.payOrderClicked ->{
                println("Sending form with parameters:${formUiState.name} and phone ${formUiState.phone}")
            }
        }
    }
    //Business's logic or maybe some UI's logic for update the state
    companion object{
        fun applyLogicToValidateInputs(name:String,phone:String):Boolean{
            return name.length>1 && phone.length ==11
        }
    }
}






data class FormUiSatate6(val name:String="",val phone:String="")
val FormUiSatate6.successValidated:Boolean get()=name.length>1 && phone.length==11





sealed class FormUiEvent{
    data class onNameChange (val name:String):FormUiEvent()
    data class onPhoneChange(val phone:String):FormUiEvent()
    object payOrderClicked: FormUiEvent()
}




注意:

有些可能已经注意到,我将字段验证逻辑包含在了FormUiState状态结构中。 由于逻辑通常比验证字符长度更复杂,最好将验证和验证任务委托给ViewModel。 因此,我们在ViewModel和FormUiState中添加了以下改变:

	// Business's logic or maybe some UI's logic for update the state
	companion object {
	fun applyLogicToValidateInputs(name: String, phone: String): Boolean {
	return name.length > 1 && phone.length > 3
	}
	}





data class FormUiState(
	val name: String = "",
	val phone: String = ""
	)

val FormUiState.successValidated: Boolean get() =OrderViewModel.applyLogicToValidateInputs(name, phone)

现在所有的逻辑都在ViewModel端了。

总结

在这个练习中,我们回顾了使用View或ViewModel作为真实源来管理状态和事件的方法。 此外,我们使用了一些技术来更好地组织状态和事件的结构,以便有一个更好组织和易于跟踪的实现。 在下一章中,我们将看到“立即下单”应用程序的总结,这是一个电子商务应用,我们将实现它,以解释现代Android应用开发的概念和技术。

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

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

相关文章

N型光伏电池技术“两头开花”,谁是诗和远方?

光伏产业已经进入大规模、市场化发展的新阶段。 近日,国家能源局公布了上半年全国电力工业统计数据,根据总装机容量,光伏装机已正式成为我国第二大电源装机,仅次于煤电。 作为新兴产业,光伏市场持续扩容总是伴随着技…

SpringMVC----(1)基础

SringMVC 1 SpringMVC简介2 SpringMVC入门案例2.1 入门案例2.2 入门案例工作流程2.3 bean加载控制2.4 PostMan工具 3 请求与响应3.1 请求映射路径3.2 Get和Post请求发送普通参数3.3 请求头的五种类型参数传递3.4 请求体的JSON数据传输参数3.5 日期型参数3.6 响应 4 REST风格4.1…

SpringCloud整合Nacos配置中心

📝 学技术、更要掌握学习的方法,一起学习,让进步发生 👩🏻 作者:一只IT攻城狮 ,关注我,不迷路 。 💐学习建议:1、养成习惯,学习java的任何一个技术…

NoSQL之Redis配置使用

目录 一、关系数据库与非关系型数据库 1.1.关系型数据库的概述 1.2关系型数据库的优缺点 1.2.1优点 1.2.2缺点 1.3.非关系型数据库的概述 二.关系数据库与非关系型数据库的区别 2.1数据存储方式不同 2.2扩展方式不同 2.3对事务性的支持不同 2.4非关系型数据库产生背景 2…

亚马逊攀岩绳EN892:2012+A1:2016安全带标准、攀岩安全带EN 12277:2015登山装备要求和ASTM F1772–17体育运动安全标准规范

如果您在亚马逊商城发布商品,则必须遵守适用于这些商品和商品信息的所有联邦、州和地方法律以及亚马逊政策(包括本政策)。 本政策适用的攀岩安全带 本政策适用于主要在攀岩或登山期间使用且使用者双脚不接触地面时使用的安全带。安全带是一种…

Xshell使用sftp传输文件

单击工具栏新建回话图标,在弹出的新建回话窗口中协议选择SFTP,输入主机名或ip地址,端口号22,单击连接,输入用户名和密码完成创建连接。 本地/远程目录设置:新建会话时在下图中SFTP中设置文件上传下载的本地…

基于vue+element 分页的封装

目录标题 项目场景:认识分页1.current-page2.page-sizes3.page-size4.layout5.total6.size-change7.current-change 封装分页:创建paging:进行封装 页面中使用:引入效果 项目场景: 分页也是我们在实际应用当中非常常见…

Nginx与Tomcat服务器的区别以及个人网站部署方案

- Nginx和Tomcat作用一样吗? 答:不完全相同。Nginx 和 Tomcat 都可以作为 Web 服务器,但它们的作用略有不同。 Nginx 是一个高性能的 Web 服务器和反向代理服务器。它的主要作用是提供静态文件服务、反向代理、负载均衡、缓存、SSL 加密等功…

《PyTorch深度学习实践》

文章目录 1.线性模型2.梯度下降算法3.反向传播3.1原理3.2Tensor in PyTorch 4.用PyTorch实现线性模型 1.线性模型 2.梯度下降算法 # 梯度下降x_data [1.0,2.0,3.0] y_data [2.0,4.0,6.0]w 3.0def forward(x):return x*w# 损失函数 def cost(xs,ys):cost 0for x,y in zip(x…

VR全景在酒店的发展状况如何?酒店该如何做营销?

现阶段,VR全景技术已经被酒店、民宿、旅游景区、房产楼盘、校园等行业所应用,每天都有不少人通过VR全景展示来了解酒店的设施环境,而酒店也可以借此机会,详细展示自身优势,更大范围吸引顾客。 VR酒店拥有真实、立体的全…

某商业落地充电桩后台服务器通迅协议V4.9.

充电机智能终端与智能中心管理系统 通迅协议 目录 一、网络拓扑 4 1.1 功能界定 4 1.1.1 充电机智能终端 4 1.1.2 智能中心管理系统 4 1.2 接口定义 4 1.3 通信方式 4 1.4 通信规约 5 1.5 报文格式 7 1.6 关键命令 8 二、应用层 9 2.1 数据格式: 9 2.2…

全面解析Bootstrap排版使用方法(文字样式)

全面解析Bootstrap排版使用方法(文字样式) 一、段落 段落是排版中另一个重要元素之一。在Bootstrap中为文本设置了一个全局的文本样式(这里所说的文本是指正文文本): 1、全局文本字号为14px(font-size)。 2、行高为…

bash: /etc/profile: 权限不够

添加环境变量的时候显示没有权限 echo “export PATH”/usr/local/ARM-toolchain/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/:$PATH"" >> /etc/profile bash: /etc/profile: 权限不够 sudo chmod 777 /etc/profile echo “export PATH”/us…

Flink任务优化分享

Flink任务优化分享 1.背景介绍 线上计算任务在某版本上线之后发现每日的任务时长都需要三个多小时才能完成,计算时间超过了预估时间,通过Dolphinscheduler的每日调度任务看,在数据层 dwd 的数据分段任务存在严重的性能问题,每天…

安装requests模块及其他依赖库的完美解决办法

业务场景 导入requests库时报错,单独离线下载安装requests,发现仍然报错,问题在于requests库有其他依赖库。 WARNING: Retrying (Retry(total1, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnect…

【历史上的今天】7 月 24 日:Caldera 诉微软案;AMD 宣布收购 ATI;谷歌推出 Chromecast

整理 | 王启隆 透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。 今天是 2023 年 7 月 24 日,在 1951 年的今天,晶体管发明家 John Bardeen 通知 AT&T 贝尔实验室,他将离开公司,与 Walter B…

PaddleOCR #PP-OCR常见异常扫雷

异常一:ModuleNotFoundError: No module named ‘tools.infer’ 实验案例: PaddleOCR #使用PaddleOCR进行光学字符识别(PP-OCR文本检测识别) 参考代码: 图片文本检测实验时,运行代码出现异常:M…

查看端口是否开通的方法

1.使用curl方式 curl -vv telnet://192.168.2.95:6080 在没有wget的本地化服务器中,可以使用该方法查看端口。 2.使用telnet方式 telnet ip 端口 失败的情况: 3.在安卓设备上使用adb的方式查看端口是否开放 adb shell nc ip 端口 4.还有一种思路…

PgSQL-使用技巧-如何衡量网络对性能的影响

PgSQL-使用技巧-如何衡量网络对性能的影响 PG数据库和应用之间常见的部件有连接池、负载平衡组件、路由、防火墙等。我们常常不在意或者认为涉及的网络hops对整体性能产生的额外开销是理所当然的。但在很多情况下,它可能会导致严重的性能损失和拖累整体吞吐量。相当…

Python中的datetime模块

time模块用于取得UNIX纪元时间戳,并加以处理。但是,如果以方便的格式显示日期,或对日期进行算数运算,就应该使用datetime模块。 目录 1. datetime数据类型 1) datetime.datetime.now()表示特定时刻 2)da…