Android Jetpack Compose的附带效应

news2024/11/20 3:32:17

Android Jetpack Compose的附带效应

Compose side-effect
Jetpack Compose 是许多开发人员的首选,因为它具有有趣、简单、有效和直接的特性,并且能够轻松地以声明方式构建自定义组件。但是,要充分利用其功能,重要的是要很好地掌握副作用和效果处理程序。

什么是附带效应?

在 Android 中构建 UI 时,管理副作用可能是开发人员面临的最大挑战之一。它是在可组合函数范围之外发生的应用程序状态更改。

// Side Effect
private var i = 0

@Composable
fun SideEffect() {
    var text by remember {
        mutableStateOF("")
    }
    Column {
        Button(onClick = { text += "@" }) {
            i++
            Text(text)
        }
    }
}

在这个例子中,SideEffect使用mutableStateOf创建一个可变状态对象,初始值为空字符串。现在在按钮单击时,我们正在更新文本,并且在文本更新时,我们想要更新i的值。但是Button组合可能会在没有点击的情况下重新组合,这不会改变文本,但会增加i的值。如果这是一个网络调用,那么每次Button重新组合时都会进行网络调用。

理想情况下,您的组合应该是无副作用的,但有时您需要副作用,例如触发一次性事件,例如进行网络调用或收集流。

为解决这些问题,Compose提供了各种用于不同情况的副作用,包括以下内容:

LaunchedEffect

LaunchedEffect是一个可组合函数,用于在组合范围内启动协程。当LaunchedEffect进入组合时,它会启动一个协程,在离开组合时取消它。LaunchedEffect接受多个键作为参数,如果任何一个键发生变化,它会取消现有的协程并重新启动。这对于执行副作用非常有用,例如进行网络调用或更新数据库,而不会阻塞UI线程。

// Launched Effect
private var i = 0

@Composable
fun SideEffect() {
    var text by remember {
        mutableStateOF("")
    }
    
    LaunchedEffect(key1 = text) {
        i++
    }
    Column {
        Button(onClick = { text += "@" }) {
            Text(text)
        }
    }
}

在上面的示例中,每次文本更新时,都会启动一个新的协程并相应地更新i的值。由于i只有在文本值更改时才会增加,因此此函数是无副作用的。

rememberCoroutineScope

为确保LaunchedEffect在第一次组合时启动,请按原样使用它。但是,如果需要手动控制启动,请改用rememberCoroutineScope。可以使用它来获取一个与组合点绑定的协程作用域,以在组合外部启动协程。rememberCoroutineScope是一个可组合的函数,它返回一个协程作用域,该作用域绑定到调用它的Composable的位置。当调用离开组合时,该作用域将被取消。

@Composable
fun MyComponent() {
    val coroutineScope = rememberCoroutineScope()
    val data = remember { mutableStateOf("") }

    Button(onClick = {
        coroutineScope.launch {
            // Simulate network call
            delay(2000)
            data.value = "Data loaded"
        }
    }) {
        Text("Load data")
    }

    Text(text = data.value)
}

在这个例子中,rememberCoroutineScope被用来创建一个与Composable函数的生命周期绑定的协程作用域。这样做可以通过确保当Composable函数从组合中删除时取消协程来高效且安全地管理协程。您可以在此范围内使用launch函数来轻松且安全地管理异步操作。

rememberUpdatedState

当你想在 effect 中引用一个值时,如果该值在改变时不应该重启,则可以使用 rememberUpdatedState。LaunchedEffect 会在 key 参数的任一值更新时重新启动,但有时我们希望在 effect 内部捕获更改的值而不重新启动它。如果我们有一个长时间运行的选项,重新启动会非常昂贵,这种处理方式就非常有帮助。

@Composable
fun ParentComponent() {
     setContent {
            ComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
                    var dynamicData by remember {
                        mutableStateOf("")
                    }
                    LaunchedEffect(Unit) {
                        delay(3000L)
                        dynamicData = "New Text"
                    }
                    MyComponent(title = dynamicData)
                }
            }
        }
}

@Composable
fun MyComponent(title: String) {
  var data by remember { mutableStateOf("") }

    val updatedData by rememberUpdatedState(title)

    LaunchedEffect(Unit) {
        delay(5000L)
        data = updatedData
    }

    Text(text = data)
}

初始情况下,title 是一个空字符串。在 3 秒后,title 变成了“New Text”。在 5 秒后,data 也变成了“New Text”,从而触发了 UI 的重新组合。这更新了 Text 组合。因此,总延迟时间为 5 秒,如果我们没有使用 rememberUpdatedState,那么我们必须重新启动第二个 LaunchedEffect,这将需要 8 秒。

DisposableEffect

DisposableEffect 组合函数用于在 Composable 函数最初创建时执行一个效果。当 Composable 从屏幕中移除时,它会清除效果。

@Composable
fun MyComponent() {
    var data by remember { mutableStateOf("") }
    val disposableEffect = remember { mutableStateOf<Disposable?>(null) }

    DisposableEffect(Unit) {
        val disposable = someAsyncOperation().subscribe {
            data = it
        }
        onDispose {
            disposable.dispose()
        }
        disposableEffect.value = disposable
    }

    // rest of the composable function
}

在这个例子中,我们创建了一个名为 MyComponent 的 Composable。它有两个 mutable state 变量:datadisposableEffect

在 DisposableEffect 中,我们调用了一个异步操作 someAsyncOperation(),它返回一个 Observable,在操作完成时会发出一个新值。我们订阅它并更新 data

我们还使用onDispose来处理 disposable 和停止操作,当 Composable 被移除时会被自动调用。

最后,我们将 disposableEffect 设置为 disposable 对象,以便调用 Composable 可以访问它。

SideEffect

SideEffect 用于将 Compose 状态发布给非 Compose 代码。SideEffect 在每次重新组合时触发,它不是一个协程作用域,因此不能在其中使用挂起函数。

当我最初发现这个副作用时,我对它的重要性和重要性的程度感到不确定,因此我更深入地研究了这个问题以获得更好的理解。

class Ref(var value: Int)

@Composable
inline fun LogCompositions(tag: String) {
        val ref = remember { Ref(0) }
        SideEffect { ref.value++ }
        Logger.log("$tag Compositions: ${ref.value}")
}

当effect被调用时,它会记录创建的composition数量。

produceState

produceState将非compose状态转换为compose状态。它启动一个协程,作用域是composition,可以将值推入返回状态中。当produceState进入Composition时,生产者启动;当它离开Composition时停止。返回的State组合在一起;设置相同的值不会导致重组。

下面是如何使用produceState从网络加载图像的示例。loadNetworkImage composable函数提供了可在其他composable中使用的State。此示例选自官方文档。

@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository = ImageRepository()
): State<Result<Image>> {

    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new inputs.
    return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {

        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}

derivedStateOf

derivedStateOf是一个可以用来基于其他状态变量的值派生新状态的可组合函数。当需要计算一个依赖于其他值的值时,并且希望避免不必要的重新计算,它就非常有用。

下面是一个使用derivedStateOf的示例:

@Composable
fun MyComponent() {
    var firstName by remember { mutableStateOf("") }
    var lastName by remember { mutableStateOf("") }

    val fullName = derivedStateOf {
        "$firstName $lastName"
    }

    Text(text = "Full Name: $fullName")
}

在这个例子中,我们定义了一个名为MyComponent的Composable函数,其中包含一个可变的状态变量firstNamelastName。我们使用snapshotFlow创建一个新的流变量fullName,它将firstNamelastName的值连接起来。每当firstName或lastName发生更改时,snapshotFlow重新组合Composable并更新fullName。最后,我们使用Text Composable显示fullNamesnapshotFlow可用于创建流,以响应状态的更改,而无需手动管理回调或侦听器。

snapshotFlow

snapshotFlow是一个函数,可以用于创建一个流(Flow),该流会首先发出状态对象的当前值,然后发出任何后续更改。这对于创建响应式 UI 非常有用,可以响应状态的更改,而无需手动管理回调或侦听器。

以下是在Compose中使用snapshotFlow的示例:

@Composable
fun MyComponent() {
    val count = remember { mutableStateOf(0) }

    val countFlow = snapshotFlow { count.value }

    LaunchedEffect(countFlow) {
        countFlow.collect { value ->
            // Handle the new value
        }
    }

    Button(onClick = { count.value++ }) {
        Text("Clicked ${count.value} times")
    }

在这个例子中,MyComponent使用mutableStateOf(0)创建了一个可变状态对象。然后调用snapshotFlow,传入一个lambda表达式返回状态对象的当前值。由此产生的countFlow流会发射当前值和状态对象的任何后续更改。

使用LaunchedEffect来从countFlow流中收集数据,确保只有在组件处于活动状态时才进行收集,并在移除时停止。最后,使用Button来更新状态对象的值。

参考

https://proandroiddev.com/mastering-side-effects-in-jetpack-compose-b7ee46162c01

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

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

相关文章

十六、Java数据结构与算法 - 图

文章目录 一、图的基本介绍1.1 为什么要有图1.2 图的举例说明1.3 图的常用概念 二、图的表示方式2.1 邻接矩阵2.2 邻接表 三、图的快速入门案例四、图的遍历4.1 深度优先遍历DFS4.1.1 基本思想4.1.2 算法步骤4.1.3 图示 4.2 广度优先遍历BFS4.2.1 基本思想4.2.2 算法步骤4.2.3 …

Segment Anything模型部分结构和代码解析

0x0. 前言 上个月Meta提出的Segment Anything Model&#xff08;SAM&#xff09;希望在视觉领域通过Prompt基础大模型的套路来解决目标分割的问题。经过实测&#xff0c;在大多数场景中SAM的表现都足够惊艳&#xff0c;并且基于SAM的各种二创工作也爆发了比如 检测一切的Groun…

Prometheus监控报警-web,域名,端口, 钉钉版本

Prometheus监控报警-web&#xff0c;域名&#xff0c;端口&#xff0c; 钉钉版本 采用文章 https://www.yuque.com/youngfit/qok2pe/nypstd#616a2e58https://www.jianshu.com/p/eae67b770c3ehttps://blog.csdn.net/Sebastien23/article/details/113645177https://www.cnblogs…

Unix套接字(UDS,Unix Domain Socket)

【知识简介】 在​​Linux​​系统中&#xff0c;有很多进程间通信方式&#xff0c;套接字&#xff08;Socket&#xff09;就是其中的一种。但传统的套接字的用法都是基于TCP/IP协议栈的&#xff0c;需要指定IP地址。如果不同主机上的两个进程进行通信&#xff0c;当然这样做没…

BEVFusion A Simple and Robust LiDAR-Camera Fusion Framework 论文学习

论文地址&#xff1a;BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework 论文学习 Github 地址&#xff1a;BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework 论文学习 1. 解决了什么问题&#xff1f; 将相机和 LiDAR 融合已经成为 3D 检测任务事…

【MySQL】数据库的增删改查二:CURD

目录 ​ 需要知道 &#x1f31f;一、增加数据 &#x1f308;1、语法 &#x1f308;2、单行数据&#xff0c;全列插入 &#x1f308;3、多行数据&#xff0c;全列插入 &#x1f31f;二、查询数据 &#x1f308;1、全列查询 &#x1f308;2、指定列查询 &#…

Nacos集群部署配置Nginx负载均衡

Nacos集群部署配置Nginx负载均衡 1|新建nacos文件夹 mkdir nacos 新建文件夹 cd nacos 进入文件夹2|下载Nacos安装包&#xff08;前提是云服务器&#xff0c;有网。也可以在windows下载好再上传&#xff09; wget https://github.com/alibaba/nacos/releases/download/2…

新品发布 | 12通道CAN FD转USB接口卡全新上市!

新品发布 ON 05.05 TC1018是同星智能开发的一款12路CAN FD总线转USB接口卡&#xff0c;配合我们的TSMaster软件可以监控、分析和仿真CAN FD总线数据。广泛应用于汽车、工业、特种机械和其他行业&#xff0c;用于CAN总线测试与分析、UDS诊断和ECU刷写等方面。 TC1018-产品简介…

weka3.8.6的安装与使用

目录 背景 一、安装 二、使用explorer 1. 介绍 2.打开自带的数据集(Preprocess) 1.打开步骤 2.查看属性和数据编辑 3.classify 4.Cluster 5.Associate 6.Select attributes 7.Visualize 待补充 背景 Weka的全名是怀卡托智能分析环境&#xff08;Waikato Environme…

进程调度/页面置换/磁盘调度算法

进程调度算法 进程调度算法也称 CPU 调度算法&#xff0c;毕竟进程是由 CPU 调度的。 当 CPU 空闲时&#xff0c;操作系统就选择内存中的某个「就绪状态」的进程&#xff0c;并给其分配 CPU。 什么时候会发生 CPU 调度呢&#xff1f;通常有以下情况&#xff1a; 当进程从运…

AIGC:【LLM(二)】——LangChain:由LLMs驱动的应用开发框架

文章目录 一.背景介绍二.LangChain简介2.1 常见应用场景 三.LangChain特点3.1 优点3.2 不足 四.LangChain功能4.1 基础功能4.2 功能模块4.2.1 LLM和Prompts4.2.2 Chain4.2.3 Agent4.2.4 Memory4.2.5 Embedding4.2.6 Models4.2.7 Indexes 五.实战案例5.1 背景需求5.2 数据准备5.…

抖音seo矩阵系统源码是什么?

抖音SEO矩阵系统源码是一款功能强大的营销工具&#xff0c;能够帮助用户进行抖音视频的SEO优化&#xff0c;使其在抖音平台上获得更高的曝光度和流量。该系统结合了SEO的相关算法和技巧&#xff0c;提供了完整的优化方案&#xff0c;可帮助用户提高视频的曝光率、获得更多的点赞…

阻塞队列原理及Java实现

目录 1.阻塞队列 1.举例&#xff1a;包饺子 1.通过多线程来实现 2.通过阻塞队列来实现 2.消息队列 1.解耦 2.削峰填谷 用消息队列来解决 3.异步操作 3.实现一个阻塞队列 使用循环数组 4.实现生产者和消费者模型 完整代码 5.虚假唤醒 1.概念及原因 2.解决方法 1…

关于GD32替换STM32(pin to pin)搭载rt-thread操作系统,需要注意的问题总结

1、SystemInit()函数 该函数位于启动文件中的Reset_Handler中(具体实现在GD32位于system_gd32f4xx.c&#xff0c;STM32位于system_stm32f4xx.c中&#xff0c;几乎所有的文件&#xff0c;你只要把gd换成st就能找到对应的文件)&#xff0c;gd的叫startup_gd32Fxxx.s&#xff0c;…

4.HIVE函数

1.hive函数 1.1 空值替换 两个输入&#xff1a;nvl(col,default_num) : 如果colum不为null&#xff0c;返回col.否则返回default_num 多个输入&#xff1a;coalesce(col1, col2, col3, ....) &#xff1a;从左到右找第一个不为null的值 例如&#xff1a;求所有员工的平均薪…

【操作系统】总结

依旧是小林coding 的内容 存储架构 现代 CPU 都是多核心的&#xff0c;线程可能在不同 CPU 核心来回切换执行&#xff0c;这对 CPU Cache 不是有利的&#xff0c;虽然 L3 Cache 是多核心之间共享的&#xff0c;但是 L1 和 L2 Cache 都是每个核心独有的&#xff0c;如果一个线…

VMWare安装windows7虚拟机提示Operating System not found

前提&#xff1a;下载windows7 Gost并创建虚拟机&#xff0c;启动报错&#xff1a;Operating System not found 解决办法 用微PE工具制作iso系统&#xff0c;对虚拟机进行分区 下载地址&#xff1a;https://www.wepe.com.cn/ 制作方法&#xff0c;双击安装程序&#xff0c;选…

最困难的也是最简单的,做好这两点不盈利天理难容

投资者应该时刻记住&#xff0c;在外汇交易中复杂的方法并不总是最好的。Forexclub发现交易中最困难的是正确识别进场点和出场点。 从技术上来说&#xff0c;进入交易是非常容易的&#xff0c;你只需要点击一个按钮&#xff0c;你就在那里交易。但是你会从中获利吗?没人能回答…

【Linux Network】网络编程套接字(代码练习)—UDP

目录 1. 常用接口 2. C/S 回声模拟 3. C/S myshell 的制作 Linux网络编程✨ 1. 常用接口 socket&#xff1a;创建套接字&#xff1a; // 创建 socket 文件描述符 int socket(int domain, int type, int protocol); 返回值&#xff1a; 套接字创建成功返回一个文件描述符 &…

GAMMA电源维修直流高压电源模块RR300-1P

美国GAMMA高压电源维修参数&#xff08;RR分离式&#xff09;&#xff1a; 输入&#xff1a;220VAC 或 380VAC&#xff08;视型号而定&#xff09; 输出电压&#xff1a;550KV&#xff0c;功率&#xff1a;0-10KW或定制 纹波率0.01 &#xff1b;稳定度0.01/1H 控制部分19英…