Jetpack Compose中的附带效应及效应处理器

news2024/10/7 8:25:48

Jetpack Compose中的附带效应及效应处理器


将在任何可组合函数范围之外运行的代码称为附带效应。

为什么要编写在任何可组合函数范围之外的代码?

这是因为可组合项的生命周期和属性(例如不可预测的重组)会执行可组合项的重组。

让我们通过一个示例来理解为什么我们需要在Compose项目中使用附带效应。

@Composable
fun WithoutSideEffectExample() {

    var counter by remember { mutableStateOf(0) }
    val context = LocalContext.current

    // on every recomposition , this toast will show
    context.showToast("Hello")

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(onClick = { counter++ }) {
            Text(text = "Click here")
        }
        SpacerHeight()
        Text(text = "$counter")
    }

}

就像你可以在上面的例子中看到的那样,我编写的代码没有使用附带效应。这段代码有一个按钮和文本,在每次点击按钮时,计数器变量会增加并显示在Text的组合函数中。当我们首次启动应用程序时,我们还会显示一个弹出消息。

当我们首次启动应用程序时,它运行得很好,但当我们点击按钮时,计数器变量会增加,并且弹出消息会再次显示。再次点击按钮,弹出消息又会显示。请参考下面的GIF。

未使用附带效应

为什么?我们都知道,当状态改变时(这里是计数变量改变,因为它是一个可变状态),会发生重新组合(这个组合函数会重新构建)。

在这种情况下,我们必须将我们的提示代码写在附带效应内部,这样它只会运行一次,并且我们可以对该代码进行控制。

注意:永远不要在组合函数内部运行任何non-composable代码,总是使用side-effect来处理。

为了处理这些附带效应,我们有各种effect-handlersside-effect states

有两种类型的Effect-Handlers

Suspended effect handler

effect-handler用于挂起函数

  • LaunchEffect
  • rememberCoroutineScope

Non-suspended effect handler

此effect-handler用于非挂起函数

  • DisposableEffect
  • SideEffect

有四种Side-Effect States

  • rememberUpdateState
  • produceState
  • derivedStateOf
  • snapShotFlow
    让我们逐个了解所有这些

LaunchEffect

LaunchEffect 是一个可组合函数,用于在组件启动时执行副作用。它接受两个参数:key 和 coroutineScope 块。

  • key 参数中,您可以传递任何状态,因为它的类型是 Any。
  • coroutineScope 块中,您可以传递任何暂停或非暂停函数。
  • LaunchEffect 会在可组合函数中始终运行一次。

如果您希望再次运行 LaunchEffect 块,则必须在key参数中传递任何随时间变化的状态(mutableStateOf,StateFlow)。
有很多理论,让我们通过一个示例来理解

示例-1

让我们通过 LaunchEffect 解决上述的 toast 问题

@Composable
fun WithLaunchEffect() {

    var counter by remember { mutableStateOf(0) }
    val context = LocalContext.current


    LaunchedEffect(key1 = true) {
        context.showToast("Hello")
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(onClick = { counter++ }) {
            Text(text = "Click here")
        }
        SpacerHeight()
        Text(text = "$counter")
    }

}

正如您在上面的代码中所看到的,我们将showToast代码移到了launch effect中。这意味着当您首次启动应用程序时,launch effect块将在可组合函数中被调用一次。

现在Toast只显示一次,在点击按钮后对toast代码没有影响。
假设你的toast代码在启动效果中,而你希望在点击按钮时显示toast信息。让我们看看如何做到这一点?
这是一个非常简单的事情,只需将counter变量传递到key参数中即可。

@Composable
fun WithLaunchEffect() {

    var counter by remember { mutableStateOf(0) }
    val context = LocalContext.current


    LaunchedEffect(key1 = counter) {
        context.showToast("Hello")
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(onClick = { counter++ }) {
            Text(text = "Click here")
        }
        SpacerHeight()
        Text(text = "$counter")
    }

}

counter变量改变时,启动launch effect并显示提示消息。

示例-2

让我们看另一个包含 API 调用的示例。

sealed class ApiState<out T> {
    data class Success<T>(val data: String) : ApiState<T>()
    object Loading : ApiState<Nothing>()
    object Empty : ApiState<Nothing>()
}

class MainViewModel : ViewModel() {
    private val _apiState: MutableState<ApiState<String>> = mutableStateOf(ApiState.Empty)
    var apiState: State<ApiState<String>> = _apiState
        private set

    fun getApiData() = viewModelScope.launch {
        _apiState.value = ApiState.Loading
        delay(2000)
        _apiState.value = ApiState.Success("Data loaded successfully..")
    }

}

@Composable
fun LaunchEffectExample() {
    val viewModel: MainViewModel = viewModel()
    var call by remember { mutableStateOf(false) }

    LaunchedEffect(key1 = call) {
        viewModel.getApiData()
    }
// never call this function here as whenever recomposition occurs this function will call again
//    viewModel.getApiData()

    when (val res = viewModel.apiState.value) {
        is ApiState.Success -> {
            Log.d("main", "Success")
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text(text = res.data, fontSize = 25.sp)
                SpacerHeight()
                Button(onClick = {
                    call = !call
                }) {
                    Text(text = "Call Api again !")
                }
            }
        }
        ApiState.Loading -> {
            Log.d("main", "Loading")
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }
        ApiState.Empty -> {}
    }

}

如您在上述代码中所见,我们正在进行伪 API 调用,从 viewModel 中获取成功和失败状态。正如您在组合函数中注意到的那样,我们在启动效果中传递了 getApiData 函数。如果我们不这样做,我们的 API 将会一次又一次地调用,因为我们的 viewModel.apiState 值发生变化时,我们的组合函数将会重组,然后再次调用 getApiData 函数,因此这个循环将会继续下去,永远不会结束。

记住我上面说的,永远不要在composable函数内运行任何non-composable的代码,始终使用附带效应来实现。

如果你想再次调用这个API,我们可以使用可变状态变量,将其传递给键参数,每当这个变量的值改变时,getApiData函数将被调用。

rememberCoroutineScope()

这是Jetpack Compose中的一个可组合函数,它将创建与当前组合相关联的协程作用域,我们可以在其中调用任何挂起函数。

  • 此协程作用域可用于启动新的协程,当组合(可组合函数)不再活动时,这些协程将自动取消。
  • rememberCoroutineScope()创建的CoroutineScope对象对于每个组合而言是单例的。这意味着如果在同一组合中多次调用该函数,它将返回相同的协程作用域对象。

让我们通过一个示例来理解,你知道如何在Jetpack Compose中创建Snackbar吗?不知道?让我们构建它。

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun CoroutineScopeExample() {
    val state = rememberScaffoldState()
    val scope = rememberCoroutineScope()
    Scaffold(
        scaffoldState = state,
    ) {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Button(onClick = {
                scope.launch {
                    state.snackbarHostState.showSnackbar("Hey How are you?")
                }
            }) {
                Text(text = "Show Snackbar")
            }
        }
    }

}

在上面的代码中,当我们点击按钮时,它将显示一个 Snackbar

scope.launch {
    state.snackbarHostState.showSnackbar("Hey How are you?")
}

这里的.showSnackbar是一个挂起函数,意味着我们必须在协程作用域中调用它,因此我们创建了一个rememberCoroutineScope协议函数。

scope.cancel()

如果上述协程作用域在多个地方使用,并且在编写此代码时使用scope.cancel(),所有协程作用域将被取消。

val job =  scope.launch {
       state.snackbarHostState.showSnackbar("Hey How are you?")
   }
   job.cancel()

如果您想取消当前作用域,只需编写上述代码即可!

DisposableEffect

DisposableEffect 是 Jetpack Compose 的一个函数,允许您创建需要在 Composable 首次渲染或销毁时执行的副作用。

该函数接受两个参数,第一个参数是需要执行的副作用,第二个参数是触发副作用运行的依赖项列表。

让我们通过一个例子来理解,基本上在按钮点击时,我们会发送一个广播事件来检查设备是否处于飞行模式,并看看我们如何在这里使用disposable effect

@Composable
fun AirplaneModeScreen() {

    var data by remember{ mutableStateOf("No State") }
    val context = LocalContext.current
    val broadcastReceiver = remember {
        object : BroadcastReceiver(){
            override fun onReceive(context: Context?, intent: Intent?) {
                val bundle = intent?.getBooleanExtra("state",false) ?: return
                data = if(bundle)
                    "Airplane mode enabled"
                else
                    "Airplane mode disabled"
            }

        }
    }

    DisposableEffect(key1 = true){
        val intentFilter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
        context.applicationContext.registerReceiver(broadcastReceiver,intentFilter)
        onDispose {
            context.applicationContext.unregisterReceiver(broadcastReceiver)
        }
    }

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
        Text(text = data)
    }

}

如您在上面的代码中所见,我们正在发送一个广播事件来检查飞行模式。

但是,当您关注DisposableEffect的代码时,它看起来很奇怪。因此,在这个disposable effect函数中,我们注册了广播接收器,并且在其中还包含一个名为onDispose的函数,用于在不再使用时(当这个可组合函数销毁时)对广播接收器进行释放或取消注册。

注意:每当您遇到需要在不再使用时释放或取消注册某个内容的情况时,不要犹豫,只需使用DisposableEffect。希望您明白了!

SideEffect

SideEffect是Jetpack Compose中的一个函数,用于在不影响UI性能的情况下进行side work

让我们通过一个示例来了解

不使用SideEffect函数

@Composable
fun WithOutSideEffectExample() {
    val count = remember { mutableStateOf(0) }
   
    Log.d("sideeffect", "Count is ${count.value}")
    
    Button(onClick = { count.value++ }) {
        Text(text = "Click here!")
    }
}

由上面的代码,您会注意到,当我们点击按钮时,count变量会增加,重新组合会发生,并且我们会看到一个logcat消息。这段代码工作得很好,但可能会对性能产生影响。

还记得吗?不要在 composable函数内运行任何non-composable代码,总是使用副作用进行处理。

使用SideEffect函数

@Composable
fun WithOutSideEffectExample() {
    val count = remember { mutableStateOf(0) }
  
    SideEffect{
           Log.d("sideeffect", "Count is ${count.value}")
    }
    
    Button(onClick = { count.value++ }) {
        Text(text = "Click here!")
    }
}

现在上面的代码看起来很好,因为我们将 logcat 代码移到了 SideEffect 块中。

derivedStateOf()

derivedStateOf 是 Jetpack Compose 中的一个函数,用于根据其他状态或派生状态的值计算值。

换句话说,derivedStateOf 是一个函数,允许您创建一个依赖于一个或多个其他状态的状态。

让我们看一个示例以更好地理解。

示例 1

fun DerivedStateExample() {

    var counter by remember { mutableStateOf(0) }
   
    val evenOdd by remember {
        derivedStateOf {
            if (counter % 2 == 0) "even"
            else "odd"
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        Text(text = "$counter", fontSize = 30.sp)
        SpacerHeight()
        Text(text = "count is $evenOdd", fontSize = 30.sp)
        SpacerHeight()
        Button(onClick = {
            counter++
        }) {
            Text(text = "Counter")
        }

    }

}

正如您在上述代码中所看到的,我们在每次点击按钮时都会增加 counter变量。在这里,counter是一个mutable state变量,并且基于这个counter变量,我们计算出奇数或偶数,并在Text中显示出来。

val oddEvent by remember {
    mutableStateOf(
        if (counter % 2 == 0)
            "even"
        else "odd"
    )
}

如果我们试图使用mutableStateOf来计算值,它永远不会起作用。

示例-2

@Composable
fun DerivedStateOfExample() {

    var numberOne by remember { mutableStateOf(0) }
    var numberTwo by remember { mutableStateOf(0) }
    val result by remember { derivedStateOf { numberOne + numberTwo } }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        TextField(value = "$numberOne", onValueChange = { numberOne = it.toIntOrNull() ?: 0 })
        SpacerHeight()
        TextField(value = "$numberTwo", onValueChange = { numberTwo = it.toIntOrNull() ?: 0 })
        SpacerHeight()
        Text(text = "Result is : $result", fontSize = 30.sp)
    }

}

如您在上例中所见,我们有两个TextField和它们的mutableState变量(numberOne,numberTwo)。现在借助于derivedStateOf,我们对这两个变量进行计算(相加),并在Text中显示结果。

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

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

相关文章

软考高级系统架构设计师(一) 考什么

目录 一、背景 二、软考(高级)的用途 三、考什么 第一科&#xff1a;综合知识 第二科&#xff1a;案例分析 第三科&#xff1a;论文 四、系统架构设计师常见的考试内容 五、模拟与训练 一、背景 系统架构设计师&#xff0c;属于软考高级考试中的一种。 二、软考(高级)…

Node搭建前端服务Mysql数据库交互一篇搞定

目录 介绍 安装环境及数据准备 代码示例 mysql连接工具类 测试方法文件 单表总量查询 单表条件查询 新增数据 修改 删除 ​编辑 ​编辑 联表查询 联表过滤 搭配express服务搭建api使用 介绍 在前端开发中,可以使用纯node前端进行服务搭建与mysql进行数据库的交互,这样…

Bun vs. Node.js

Bun vs. Node.js 你知道 Bun 吗&#xff1f;Bun 是新的 JavaScript 运行时&#xff0c;最近在技术领域引起轰动&#xff0c;它声称比 Node.js 更好。本文将展示如何使用基准分数对其进行测试。 在本文中&#xff0c;我们将介绍最近在技术领域引起轰动的新的 Bun 运行时。我们…

螯合剂试剂:DOTA-CH2-Ph-azide(HCl salt),分子式:C21H34Cl3N7O6,的相关参数信息

文章关键词&#xff1a;双功能螯合剂&#xff0c;azide叠氮 为大家介绍&#xff08;CAS&#xff1a;N/A&#xff09;,试剂仅用于科学研究&#xff0c;不可用于人类&#xff0c;非药用&#xff0c;非食用。 分子式&#xff1a;C21H34Cl3N7O6 分子量&#xff1a;586.9 英文名称&a…

限定国家及时间|心理学老师如期赴意大利访学

S老师由于个人情况变化需要办理CSC改派&#xff0c;并限定了国家且要求年底出国。我们最终用意大利巴里大学的邀请函&#xff0c;助其成功申请了CSC改派并如期出国。 S老师背景&#xff1a; 申请类型&#xff1a; CSC访问学者 工作背景&#xff1a; 高校教师 教育背景&#…

Python基础知识进阶之数据爬虫

一、爬虫概述 爬虫是指利用网络抓取模块对某个网站或者某个应用中有价值的信息进行提取。还可以模拟用户在浏览器或者APP应用上的操作行为&#xff0c;实现程序自动化。简单来说就是我们把互联网有价值的信息都比喻成大的蜘蛛网&#xff0c;而各个节点就是存放的数据&#xff0…

如何写出让业务满意的性能测试报告

目录 前言 需求背景 测试报告的作用是什么&#xff1f; 业务团队更关注哪些内容&#xff1f; 输出让业务满意的性能测试报告 总结 前言 写出一份让业务满意的性能测试报告&#xff0c;需要充分理解和呈现测试结果&#xff0c;结合业务需求进行分析和解读。 这篇文章&am…

别再错过重要任务,手机日程提醒软件用哪个

你有没有过这样的经历&#xff0c;因为平时生活、工作中太过于忙碌&#xff0c;而错过重要任务的完成&#xff1f;相信很多人都会忘记过重要的事情&#xff0c;例如平时工作太忙了&#xff0c;而忘记了之前安排好的会议。那么在待办事项越来越多的今天&#xff0c;我们如何保证…

这几个APP,你认识多少

软件一&#xff1a;AI工具导航 功能介绍 1.书写工具 包括内容创作、语法检查、内容润色等。 2.图像工具 包括文字图片生成、插图生成、在线抠图背景去除等。 3.音频工具 包含自定义风格的音乐生成、变声、配音等。 4.视频工具 包括视频生成、文字转视频、虚拟人讲解等…

vivo 游戏黑产反作弊实践

作者&#xff1a;vivo 互联网安全团队 - Cai Yifan 在数字化、移动化的浪潮下&#xff0c;游戏产业迅速发展&#xff0c;尤其疫情过后许多游戏公司业务迎来新的增长点。 游戏行业从端游开始一直是黑灰产活跃的重要场景。近年来&#xff0c;随着互联网的发展和手机市场的不断壮…

从0到1精通自动化测试,pytest自动化测试框架,用例运行规则(二)

目录 一、用例设计原则 二、help帮助 三、按以下目录写用例 四、python -m 五、执行用例规则 六、-x 遇到错误时停止测试 七、—maxfailnum 一、用例设计原则 文件名以 test_*.py 文件和 *_test.py 以 test_ 开头的函数 以 Test 开头的类 以 test_ 开头的方法 所有的包…

前端学习——HTML

C/S架构和B/S架构 1.C/S架构&#xff1a;需要安装&#xff0c;偶尔更新&#xff0c;不跨平台 2.B/S架构&#xff1a;无需安装&#xff0c;无需更新&#xff0c;可跨平台 大型专业应用、安全性要求较高的应用&#xff0c;需要采用C/S架构 前端工程师主要研发B/S架构的应用——写…

智能照明控制系统助力某商业综合体实现“双碳”

摘要&#xff1a;智能照明是当前非常普及的一种照明控制方式。广泛使用于建筑物,景观&#xff0c;公共场所。本文以智能照明在商业综合体中的应用为基础&#xff0c;主要介绍了智能照明系统的功能与特点&#xff0c;系统运用的效果&#xff0c;在建筑自动化系统中的地位及优势等…

项目前期准备 -- 手把手教你做ssm+springboot入门后端项目黑马程序员瑞吉外卖(一)

文章目录 前言一、导学内容1.前置知识&#xff08;必备&#xff09;2.博客收获3.效果展示4.软件开发流程整体介绍4.瑞吉外卖整体项目介绍 二、开发环境搭建1.数据库环境搭建2.maven环境搭建 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&…

Zoho Bigin表现亮眼,入选国际软件测评机构多项Top榜单

科技重新赋能企业发展动力&#xff0c;数字化转型成为降本增效的好方法。 然而真正落地到企业层面&#xff0c;尤其小微企业&#xff0c;做好数字化转型&#xff0c;并不是件容易的事情&#xff0c;需要多管齐下&#xff0c;长期投入时间和资金。 小微企业由于自身业务规模及…

Golang笔记:使用json包处理JSON数据

文章目录 目的Decoding&#xff08;解析数据&#xff09;Encoding&#xff08;创建数据&#xff09;总结 目的 JSON 是一种非常流行的数据交换格式&#xff0c;是JavaScript中原生支持的一种数据&#xff0c;因为其简单方便&#xff0c;所以也经常用在不同程序、不同语言间数据…

Linux5.6 Mysql备份与恢复

文章目录 计算机系统5G云计算第四章 LINUX Mysql备份与恢复一、数据库备份的分类1. 数据备份的重要性2.数据库备份的分类3. 常见的备份方法1&#xff09;物理冷备2&#xff09;专用备份工具mysqldump或mysqlhotcopy3&#xff09;启用二进制日志进行增量备份4&#xff09;第三方…

8.2 网络分层OSI七层模型

目录 计算机网络体系结构的形成 两台计算机要互相传送文件需解决很多问题 提出了不同体系结构 国际标准&#xff1a;开放系统互连参考模型 OSI/RM 但 ISO/OSI 失败了 存在两种国际标准 协议与划分层次 协议与划分层次 协议的两种形式 层次式协议结构 划分层次的概念举…

卷积计算加速方法--分块卷积

1、大尺寸卷积存在的问题 当卷积的输入太大导致内存不够用时&#xff0c;考虑将一大块卷积分成多个小块分别进行卷积&#xff0c;相当于将原始输入分成几个小的输入经过同一组卷积核分别卷积&#xff0c;其中每块小的输入都是原始输入的子集&#xff0c;每块之间互不影响&#…

OpenHarmony社区运营报告(2023年5月)

本月快讯 ● 2023年6月11-13日&#xff0c;2023开放原子全球开源峰会即将在北京北人亦创国际会展中心盛大开幕。2023开放原子全球开源峰会上&#xff0c;OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;将通过分论坛与展览等方式&#xff0c;展示生态进展…