Jetpack Compose中的附带效应简介及使用

news2024/11/20 20:26:11

前言

  附带效应是指LaunchedEffect、DisposableEffect、rememberCoroutineScope、rememberUpdatedState、produceState 、derivedStateOf的使用。附带效应这4个字在google官方文档上的表达与解释挺让人难以理解的。其实个人认为准确的描述应该是外部产生的数据向Compose状态作用域内传递。这里的外部数据是指网络请求数据、数据库数据、定时触发状态、子线程运算数据、Activity生命周期等等这些数据。

  在学了Compose的状态管理后可以明白,Compose的重组需要依靠mutableStateOf 。那为什么还需要有LaunchedEffect、DisposableEffect等等这些函数呢?理由如下:

  • 在Compose作用域里实现协程与异步操作,从而对一些资源的初始化与销毁进行异步操作
  • 接收外部数据回调,但是建议尽量选择用协程取代接口回调方法
  • 配合LifeCycle,让Compose对Activity的生命周期实现监听(这个其实与第二点其实是一样的,但是使用情况比较多这里单独拿出来强调)
  • 进一步的控制Compose重组

         个人的一些见解,这些见解你可以不认同。因为可能是我前期的粗浅认识。我认为Compose的附带效应各种方法很多,官方文档的举例也非常难以理解。其实根本原因是附带效应的各色方法使用场景其实有一部分相当重叠,容易让人一时无法判断我学习到的这些方法应该在什么情况下使用。有一些人会认为其实只要有DisposableEffect就能解决全部Compose里需要的协程异步、数据获取、资源初始化与释放完全不需要依靠其他方法,这个我也无法否认因为DisposableEffect的确可以满足以上全部功能。为什么附带效应会有这么多使用重叠的方法?我个人理解是jetpack的开发组,内部就有各色开发人员倾向不同的架构与思想。比如有人喜欢直接在Activity请求网络数据,有些人喜欢用MVP、MVC、MVVM这些架构请求网络数据、有一些人则倾向用RxJava、flow这种链式编程请求网络数据。他们为了满足这些干脆搞了一个大杂烩,让各种使用场景都能有各色附带效应方法使用。

        所以,我这里建议是你需要全部学会附带效应的方法。但是使用的时候只需要挑两三个(这里推荐LaunchedEffect、rememberCoroutineScope、DisposableEffect)你习惯使用的就行了... 不要过度思考什么场景使用什么附带效应方法。有些东西你会有一天突然醒悟的. 在醒悟之前用你最习惯最稳妥的方式写是最安全的。

LaunchedEffect

LaunchedEffect的作用主要是让你在Compose作用域里实现协程工作,以完成异步、耗时数据处理、网络请求等等工作。

简单的延迟计数

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

        LaunchedEffect(true) {
            while (true) {
                delay(1000)
                count.value++
            }
        }

        Text(text = "${count.value}", fontSize = 100.sp)

    }

效果图:

模拟请求网络数据

    @Preview()
    @Composable
    fun MyLaunchedEffect() {
        //是否请求数据
        val isPostData = remember {
            mutableStateOf(false)
        }
        //请求结果
        val postResult = remember {
            mutableStateOf("")
        }

        if (isPostData.value) {
            LaunchedEffect(isPostData.value) {
                //这里进行耗时操作处理异常,比如请求网络,从数据库获取数据
                postResult.value = withContext(Dispatchers.Default){
                    delay(1000)
                    return@withContext "模拟请求结果>>成功 ${System.currentTimeMillis()}"
                }
                //请求完成后重新设置成false,这样就可以继续下一次请求
                isPostData.value = false
            }
        }

        Button(onClick = { isPostData.value = true }) {
            Text(text = postResult.value)
        }
    }

 

效果图:

LaunchedEffect参数的作用

其实很简单,如果参数一样,那么LaunchedEffect只会触发一次,如果参数一直变化则会多次触发。但是如果你希望LaunchedEffect在指定情况下触发你还是需要向上面的例子里一样添加if。 否则LaunchedEffect在Compose创建的时候会立刻触发。

 

    var countKey1 = 0

    @Preview()
    @Composable
    fun MyLaunchedEffect() {
        val countKey2 = remember {
            mutableStateOf(0)
        }
        LaunchedEffect("A") {
            Log.e("zh", "MyLaunchedEffect: A")
        }
        LaunchedEffect(countKey1) {
            Log.e("zh", "MyLaunchedEffect: B")
        }
        LaunchedEffect(countKey2.value) {
            Log.e("zh", "MyLaunchedEffect: C")
        }
        Button(onClick = {
            countKey1++
            countKey2.value++
        }) {
        }
    }

连续点击后按钮效果如下,在下面的log里可以看到A触发了一次,B和C每次点击后都触发。这里需要使用mutableStateOf创建的countKey2来触发Compose的重组,从而验证普通Int值的countKey1也能触发LaunchedEffect的执行。

rememberCoroutineScope

rememberCoroutineScope 其实与 LaunchedEffect 功能上一致,但是LaunchedEffect 只能在compose的作用域中调用,无法在按键点击回调等等地方调用,rememberCoroutineScope可以在这些地方调用。此外,如果您需要手动控制一个或多个协程的生命周期,请使用 rememberCoroutineScope,例如在用户事件发生时取消动画。

下面的代码模拟了一个网络请求,其中引用了 implementation 'androidx.compose.runtime:runtime-livedata:1.2.1' ,把LiveData 转成 MutableState

代码:

ViewModel

class MainViewModel: ViewModel() {
    private val _data = MutableLiveData<String>()
    val data : LiveData<String> get() = _data


    suspend fun postData(){
        delay(500)
        //模拟网络的异步请求
        _data.postValue("当前时间戳 = ${System.currentTimeMillis()}")

    }
}

activity

    @Preview()
    @Composable
    fun MyCoroutineScope() {
        val coroutineScope = rememberCoroutineScope()
        val data = mViewModel.data.observeAsState()

        Column {
            Button(onClick = {
                coroutineScope.launch {
                    mViewModel.postData()
                }
            }) {
            }
            Text(text = data.value ?: "")
        }
    }

rememberUpdatedState

rememberUpdatedState 只是辅助LaunchedEffect向外部(其他composeable方法)更新状态功能,用于在不同compose方法下LaunchedEffect的数据更新时提供最新数据。下面提供2个例子代码来验证效果。

不使用rememberUpdatedState 

    @Composable
    fun MyTextField() {
        val text = remember {
            mutableStateOf("")
        }
        MyLaunchedEffect(content = text.value)
        Column {
            TextField(value = text.value, onValueChange = {
                text.value = it
            })
        }
    }

    @Composable
    fun MyLaunchedEffect(content:String){
        Log.e("zh", "MyLaunchedEffect重组中 content = ${content}")
        LaunchedEffect(Unit){
            while (true){
                delay(500)
                Log.e("zh", "content = ${content}")
            }
        }
    }

在输入框输入内容后,在日志里会反复打印代码。并没有新的 content 内容输出。

使用rememberUpdatedState 

    @Composable
    fun MyTextField() {
        val text = remember {
            mutableStateOf("")
        }
        MyLaunchedEffect(content = text.value)
        Column {
            TextField(value = text.value, onValueChange = {
                text.value = it
            })
        }
    }

    @Composable
    fun MyLaunchedEffect(content:String){
        val contentUpdatedState by rememberUpdatedState(content) //添加rememberUpdatedState
        Log.e("zh", "MyLaunchedEffect重组中 content = ${contentUpdatedState}")
        LaunchedEffect(Unit){
            while (true){
                delay(500)
                Log.e("zh", "content = ${contentUpdatedState}")
            }
        }
    }

在输入框输入内容后,日志里会打印输入的最新内容。

DisposableEffect

DisposableEffect 必须添加一个onDispose方法 ,此方法一般用来处理资源清理与释放。 比如监听了生命周期、监听了广播等等情况。另外DisposableEffect 也带参数意义和LaunchedEffect的参数一样。

下面举例了官方文档上的监听了生命周期代码例子:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HomeScreen(LocalLifecycleOwner.current, {
                //onStart
                Log.e("zh", "触发了onStart生命周期" )

            }, {
                //onStop
                Log.e("zh", "触发了onStop生命周期" )
            })
        }
    }

    @Composable
    fun HomeScreen(
        lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
        onStart: () -> Unit, 
        onStop: () -> Unit
    ) {
        //这里创建了2个更新状态,用于更新start生命周期与stop生命周期的回调
        val currentOnStart by rememberUpdatedState(onStart)
        val currentOnStop by rememberUpdatedState(onStop)

        DisposableEffect(lifecycleOwner) {
            //创建一个Activity生命周期观察者
            val observer = LifecycleEventObserver { _, event ->
                if (event == Lifecycle.Event.ON_START) {
                    currentOnStart()
                } else if (event == Lifecycle.Event.ON_STOP) {
                    currentOnStop()
                }
            }

            //添加监听
            lifecycleOwner.lifecycle.addObserver(observer)

            //在onDispose方法里移除了生命周期监听
            onDispose {
                lifecycleOwner.lifecycle.removeObserver(observer)
            }
        }
    }
}

SideEffect

SideEffect 其实就是不带参数的LaunchedEffect,并且SideEffect会在每一次重组的时候都触发。

代码:

    @Preview
    @Composable
    fun MySideEffect() {
        val count = remember { mutableStateOf(0) }
        SideEffect {
            Log.e("zh", "SideEffect触发${count.value}")
        }
        Text(text = count.value.toString(),fontSize = 36.sp, modifier = Modifier.clickable {
            count.value++
        })
    }

produceState

produceState将非 Compose 状态转换为 Compose 状态,produceState的使用场景其实与LaunchedEffect多多少少有些重叠。google设计produceState的目的应该是使用在大量重复的网络请求情况下,比如加载网络图片、下载音频视频。这类情况有一个共同场景你可能不需要mutableStateOf 来保存数据。

下面的例子将网络请求的结果转换成Compose的状态,

代码:

    @Composable
    fun PostState() {
        var updatePostData by remember {
            mutableStateOf(true)
        }
        val state =  produceState<Result<String>>(initialValue = Result(-1,"请求中", ""), updatePostData) {
            value = if (true) { //模拟网络请求请求结果
                Result(200,"请求成功", "{\"id\": \"1\",\"name\": \"小明\", \"time\":${System.currentTimeMillis()}}")
            } else {
                Result(400,"请求失败", "")
            }
        }
        Text(text = "${state}", modifier = Modifier.clickable {
            updatePostData = !updatePostData
        })
    }

附带效应重组后什么时候执行

重组触发顺序如下:

首先携带状态数据触发重组的View >> Compose的其他View重组 >>  DisposableEffect >> SideEffect >> LaunchedEffect

代码:

    @Preview
    @Composable
    fun MyRecombine() {
        val count = remember { mutableStateOf(0) }
        Log.e("zh", "外部重组1111")

        LaunchedEffect(count.value){
            Log.e("zh", "LaunchedEffect: 重组3333")
        }
        SideEffect {
            Log.e("zh", "SideEffect: 重组2222")
        }
        DisposableEffect(count.value){
            Log.e("zh", "DisposableEffect: 重组4444")
            onDispose {
            }
        }
        Log.e("zh", "外部重组5555")
        Column {
            Log.e("zh", "Column内部重组6666")
            Text(text = "重组",fontSize = 36.sp)
            Text(text = count.value.toString(),fontSize = 36.sp, modifier = Modifier.clickable {
                count.value++
                Log.e("zh", "Text: 重组7777")
            })
        }
    }

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

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

相关文章

chatgpt赋能Python-python3_8_1怎么用

Python3.8.1使用指南&#xff1a;让您的编程更加高效 Python是一种流行的高级编程语言&#xff0c;它以简洁明了的语法和丰富的库而著称。如果您是一名新手或有经验的程序员&#xff0c;Python都是一个很好的选择。在Python3.8.1中&#xff0c;新的功能和改进将进一步提高Pyth…

chatgpt赋能Python-python2的n次方程序

Python2的N次方程序&#xff1a;一个实用的编程工具 Python2是广泛使用的编程语言之一&#xff0c;它是一种强大且灵活的开源语言&#xff0c;被广泛应用于科学计算、数据分析、Web开发等领域。在Python2中&#xff0c;N次方程序是一种非常基础的程序&#xff0c;但是它在实际…

二叉树的基本认识(一)

要了解二叉树&#xff0c;就必然要知道什么是树&#xff0c;树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的…

C++类模板的具体化

目录 分类 代码 分析 一、类模板 一&#xff09;代码 二&#xff09;注意事项 三&#xff09;运行结果 二、完全具体化的模板类 一&#xff09;代码 二&#xff09;注意事项 三&#xff09;执行结果 三、部分具体化的模板类 一&#xff09;代码 二&#xff09;注…

【研发工具】Yapi接口管理平台内网Centos8下搭建

1 环境依赖安装 环境要求 nodejs&#xff08;7.6) &#xff08;本文安装12.18.3&#xff09; mongodb&#xff08;2.6&#xff09;&#xff08;本文安装5.0.17&#xff09; 1.1 安装Nodejs 1.1.1 下载安装包 下载地址&#xff1a;https://nodejs.org/zh-cn/download/这里下载…

代码随想录训练营Day42|背包问题

目录 学习目标 学习内容 416. 分割等和子集 学习目标 01背包问题&#xff0c;你该了解这些&#xff01; 01背包问题&#xff0c;你该了解这些&#xff01; 滚动数组 416. 分割等和子集 学习内容 problems/背包理论基础01背包-1.md programmercarl/leetcode-master&#…

Java【网络编程2】详解ServerSocket和Socket类, 逐行代码解析如何服务器客户端通信(附代码)

文章目录 前言一、认识 Socket(套接字), TCP 协议和 UDP 协议1, 什么是 Socket(套接字)2, 浅谈 TCP 协议和 UDP 协议的区别和特点 二、基于 TCP 协议的 Socket API1, ServerSocket 类2, Socket 类 三、逐行代码解析网络编程1, 逐行解析客户端1.1, 核心成员方法 start() 2, 逐行…

【黑马笔记】Servlet简易教程

1. Servlet demo 0. 新建web app项目 记得去 web.xml 中删除多余的配置&#xff0c;以及新建对应的文件夹 1. 导入 Servlet依赖坐标 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version&…

四块ACM区域赛金牌,我队友

是的&#xff0c;毕业12年以后&#xff0c;他来找我。     痛失网名了属于是&#xff0c;但是这不重要&#xff0c;​怎么说呢&#xff1f;有点激动&#xff0c;我得把这件事情记录下来&#xff0c;这是一位重量级的人物&#xff0c;也是大家眼中别人家的孩子。     他…

Python网络爬虫:Scrapy和Beautiful Soup的使用和数据处理技巧

章节一&#xff1a;引言 在当今互联网时代&#xff0c;数据的价值越来越被重视&#xff0c;而网络爬虫作为一种强大的工具&#xff0c;可以帮助我们从互联网中提取有用的数据。Python作为一门广泛应用于数据科学和网络开发的编程语言&#xff0c;有着丰富的库和框架来支持网络…

SD卡数据恢复软件哪个最好 SD卡数据恢复多少钱

SD卡是一种常见的数字存储卡&#xff0c;由于其体积小、存储能力强&#xff0c;至今仍然被应用在手机&#xff0c;行车记录仪&#xff0c;微型摄像机中。但SD卡在使用过程中可能会出现一些故障或者数据丢失的情况。那么今天小编就给大家介绍一下SD卡数据恢复软件哪个最好&#…

泊松分布和指数分布的关系

泊松分布和指数分布的关系 泊松分布用于描述单位时间&#xff08;或面积内&#xff09;随机事件发生的次数&#xff08;离散型随机变量&#xff09;&#xff0c; λ \lambda λ表示随机事件在单位时间&#xff08;或面积内&#xff09;发生的平均次数 The Poisson distributio…

操作系统进程调度算法——先来先服务、时间片轮转、优先级调度算法

一、先来先服务调度算法 &#xff08;1&#xff09;算法内容&#xff1a;先来先服务调度算法是一种最简单的调度算法&#xff0c;可以应用于高级调度也可以运用于低级调度。高级调度时&#xff0c;FCFS调度算法按照作业进入后备作业队列的先后顺序选择作业进入内存&#xff0c…

FL Studio 20汉化补丁及详细激活使用说明/fl studio21怎么设置中文?

音乐在人们心中的地位日益增高&#xff0c;近几年音乐选秀的节目更是层出不穷&#xff0c;喜爱音乐&#xff0c;创作音乐的朋友们也是越来越多&#xff0c;音乐的类型有很多&#xff0c;好比古典&#xff0c;流行&#xff0c;摇滚等等。对新手友好程度基本上在首位&#xff0c;…

AI新风向标PaLm2?能否取代ChatGPT成为AI领域的老大

AI新风向标PaLm2&#xff1f; ​ 前几天Google发布了&#xff0c;PaLM2作为Google的下一代大型语言模型&#xff0c;它会取代ChatGPT成为更智能的AI工具吗? 关于PaLM2 ​ PaLM 2是Google的下一代大型语言模型&#xff0c;它建立在谷歌在机器学习和负责任的人工智能方面的突破…

新一代硬件安全第5章 TRNG

title: “第5章 真随机数生成器的本征熵” author: date: 2023-05-18 output: word_document Chapter 5 Intrinsic Entropy for True Random Number Generation 5.1 Chapter Introduction True Random Number Generators (TRNGs) form an essential and indispensable part …

【LLM系列之底座模型对比】LLaMA、Palm、GLM、BLOOM、GPT模型结构对比

LLama [GPT3] 使用RMSNorm&#xff08;即Root Mean square Layer Normalization&#xff09;对输入数据进行标准化&#xff0c;RMSNorm可以参考论文&#xff1a;Root mean square layer normalization。[PaLM]使用激活函数SwiGLU&#xff0c; 该函数可以参考PALM论文&#xff…

Java:异常

异常:就是代表程序出现的问题 作用&#xff1a; 作用一:异常是用来查询bug的关键参考信息作用二:异常可以作为方法内部的一种特殊返回值&#xff0c;以便通知调用者底层的执行情况&#xff08;抛异常&#xff09; 1.Error:代表的系统级别错误 &#xff08;属于严重问题) 2.…

Jetpack Compose中的列表控件LazyRow和LazyColumn详解

背景 如果你需要显示大量的条目&#xff08;或一个未知长度的列表&#xff09;&#xff0c;使用像 Column 这样的布局会导致性能问题&#xff0c;因为所有的条目都会被组合和布局&#xff0c;无论它们是否可见。那么&#xff0c;在Compose中有没有像RecycleView的控件可以滑动…

python类的高级函数

类的高级函数 __str__ 如果定义了该函数&#xff0c;当print当前实例化对象的时候&#xff0c;会返回该函数的return信息 用法&#xff1a; def __str__(self): return str_type 参数&#xff1a;无 返回值&#xff1a;一般返回对于该类的描述信息 __getattr__ 当调用的…