1. 前言

在Compose基础-Side-effect(一)中,我们学习了几个常用的Side-effectLaunchedEffectrememberCoroutineScope,以及关键字rememberUpdateState的用法。在本篇文章中,我们将介绍剩下几个常用的Side-effect相关关键字的用法。

2. DisposableEffect

对于有些Side-effect,在其key值变化或者composable函数离开Composition时,需要对一些资源等进行清理。这种情况下,可以使用谷歌提供的DisposableEffect。其特点如下:

  • DisposableEffect的key值变化时,当前EffectonDispose会被调用,此时可以在此函数中对资源进行清理;同时DisposableEffect会被重启,此时可以重新申请资源等。

  • DisposableEffect中必须包含onDispose语句,否则IDE会出现编译时错误。

以下是使用DisposableEffect的一个示例,该DisposableEffectLifecycleOwner注册了回调,用于监听一些生命周期事件便于数据统计。代码如下:

@Composable
fun HomeScreen(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,onStart: () -> Unit, // Send the 'started' analytics eventonStop: () -> Unit   // Send the 'stopped' analytics event
) {// Safely update the current lambdas when a new one is providedval currentOnStart by rememberUpdatedState(onStart)val currentOnStop by rememberUpdatedState(onStop)// If `lifecycleOwner` changes, dispose and reset the effectDisposableEffect(lifecycleOwner) {// Create an observer that triggers our remembered callbacks// for sending analytics eventsval observer = LifecycleEventObserver { _, event ->if (event == Lifecycle.Event.ON_START) {currentOnStart()} else if (event == Lifecycle.Event.ON_STOP) {currentOnStop()}}// Add the observer to the lifecyclelifecycleOwner.lifecycle.addObserver(observer)// When the effect leaves the Composition, remove the observeronDispose {lifecycleOwner.lifecycle.removeObserver(observer)}}/* Home screen content */
}

DisposableEffect常用于抽取一些需要在composable函数离开Composition或者key值变化时清理资源的场景,例如监听的反注册。

3. SideEffect

有时候,我们需要将Compose状态共享给非Compose管理的对象。此时,Google建议我们使用SideEffect函数,因为每次recompose成功时都会调用该函数。SideEffect特点如下:

  • Composition操作失败时,能保证SideEffect中的非Compose管理对象的状态和Composition中的状态一致。

  • SideEffectLayoutNode被创建并插入layout tree后才会被调用。

以下是使用SideEffect的一个示例。分析库允许通过将自定义元数据(在此示例中为“用户属性”)附加到所有后续分析事件,来细分用户群体。如需将当前用户的用户类型传递给分析库,需要使用 SideEffect 更新其值。

@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {val analytics: FirebaseAnalytics = remember {/* ... */}// On every successful composition, update FirebaseAnalytics with// the userType from the current User, ensuring that future analytics// events have this metadata attachedSideEffect {analytics.setUserProperty("userType", user.userType)}return analytics
}

4. produceState

有时候我们会使用协程来获取数据,例如Flow,但是该数据是在Composition中使用。为了使用这些数据,我们需要使用produceState来将non-Compose state来转变为Compose stateproduceState特点如下:

  • produceState进入Composition时,获取数据的任务被启动,当其离开Composition时,该任务被取消。

  • 尽管produceState创建了一个协程,它也可以用于获取non-suspending数据源。

以下示例展示了如何利用produceState从网络加载一张图片。loadNetworkImage这个函数返回了一个State,这个State可以被用于其他composable函数。

@Composable
fun loadNetworkImage(url: String,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 callsval image = imageRepository.load(url)// Update State with either an Error or Success result.// This will trigger a recomposition where this State is readvalue = if (image == null) {Result.Error} else {Result.Success(image)}}
}

5. derivedStateOf

当一个状态由另外几个状态计算或者推导得到时,使用derivedStateOf来记录结果状态,此时作为条件的状态我们称为条件状态。当任意一个条件状态更新时,结果状态都会重新计算。以下是一个示例。

@Composable
fun DevitedStateCompose() {val input1: MutableState<String> = remember{mutableStateOf("")}val input2: MutableState<String> = remember{mutableStateOf("")}val result1: State<String>= derivedStateOf{"${input1.value} + ${input2.value}"}Column{ContentWrap{Column{TextField(value = input1.value,onValueChange =  {input1.value = it })TextField(value = input2.value,onValueChange = {input2.value = it })Log.d("DevitedStateTest", "recompose Column")}}ContentWrap{Text(result1.value)Log.d("DevitedStateTest", "ContentWrap button")}}Log.d("DevitedStateTest", "recompose DevitedStateCompose")
}@Composable
fun ContentWrap(content: @Composable () -> Unit) {content()
}

input1或者input2有变化时,result1会被重新计算。此时ContentWrap button会被打印,但是recompose DevitedStateCompose不会被打印。即展示结果的ContentWraprecompose,但是DevitedStateCompose函数未recompose

6. snapshotFlow

由于Flow强大的数据操作能力,有时候我们想将State转化为Flow,此时我们需要使用snapshotFlow来将State转化为FlowsnapshotFlow的特点如下:

  • snapshotFlow中读取的State发生变化时,snapshotFlow会重新生成Flow

以下是一个示例。

val listState = rememberLazyListState()LazyColumn(state = listState) {// ...
}LaunchedEffect(listState) {snapshotFlow { listState.firstVisibleItemIndex }.map { index -> index > 0 }.distinctUntilChanged().filter { it == true }.collect {MyAnalyticsService.sendScrolledPastFirstItemEvent()}
}

在上述代码中,如果firstVisibleItemIndex发生了变化,那snapshotFlow会将listState.firstVisibleItemIndex转化为Flow

7. 小结

本文主要介绍了DisposableEffectSideEffectproduceStatederivedStateOfsnapshotFlow五种Side-effect的作用和使用场景。其重点如下:

  • DisposableEffect用于在离开composition时清理一些资源。

  • SideEffect用于将Compose状态共享给非Compose管理的对象。

  • produceState用于将non-Compose state来转变为Compose state

  • derivedStateOf用于记录几个状态计算或者推导得到的状态。

  • snapshotFlow用于将State转化为Flow

8. 参考文档

Side-effects in Compose

Tricky refactoring of Jetpack Compose code — be careful with side effects

Compose基础-SideEffect(二)相关推荐

  1. 【C++自我精讲】基础系列二 const

    [C++自我精讲]基础系列二 const 0 前言 分三部分:const用法.const和#define比较.const作用. 1 const用法 const常量:const可以用来定义常量,不可改变 ...

  2. java负数右移_收入囊中篇---Java程序基础(二)

    前言: 本篇是接着上一篇更新的,如果没有阅读上一篇的话,可以查阅或回顾一下. 1.收入囊中篇---Java基础必备知识(一) 2.收入囊中篇---Java程序基础(二) Java程序基础目录 1.Ja ...

  3. mysql 基础篇(二) 账号、权限管理

    mysql 基础篇(二) 账号.权限管理.备份与还原 建立账号密码: Grant all on test.* to "cj"@"localhost" ident ...

  4. JVM 内部原理(七)— Java 字节码基础之二

    JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  5. CV:计算机视觉技术之图像基础知识(二)—图像内核的可视化解释

    CV:计算机视觉技术之图像基础知识(二)-图像内核的可视化解释 目录 图像内核的可视化解释 测试九种卷积核 官方Demo DIY图片测试 DIY实时视频测试 相关文章 CV:计算机视觉技术之图像基础知 ...

  6. CV:计算机视觉技术之图像基础知识(二)—以python的skimage和numpy库来了解计算机视觉图像基础(图像存储原理-模糊核-锐化核-边缘检测核,进阶卷积神经网络(CNN)的必备基础)

    CV:计算机视觉技术之图像基础知识(二)-以python的skimage和numpy库来了解计算机视觉图像基础(图像存储原理-模糊核-锐化核-边缘检测核,进阶卷积神经网络(CNN)的必备基础) 目录 ...

  7. MySQL基础总结(二)

    MySQL基础总结(二) 文章目录 MySQL基础总结(二) 四.索引 7.MyISAM主键索引与辅助索引的结构 8.InnoDB主键索引与辅助索引的结构 **`主键索引`** **`辅助(非主键)索 ...

  8. 网络基础(二)及HTTP协议

    网络基础(二)及HTTP协议 文章目录 网络基础(二)及HTTP协议 一.HTTP协议 二.端口 三.udp协议 四.tcp协议 一.HTTP协议 1 . 什么是url? 平时我们俗称的 " ...

  9. 计算机应用基础第二版在线作业c,计算机应用基础作业二(答案)

    计算机应用基础作业二 一.单选题(40题,每题1分,共40分) 1.第一台电子数字计算机的运算速度为每秒______. A:5,000,000次 B:500,000次 C:50,000次 D:5000 ...

  10. (五)JS基础知识二(通过图理解原型和原型链)【三座大山之一,必考!!!】

    JS基础知识二(原型和原型链) 提问 class 继承 类型判断(instanceof) 原型 原型关系 基于原型的执行规则 原型链 说明 提问 如何准确判断一个变量是不是数组 class的原型本质 ...

最新文章

  1. 前端html5的框架有哪些,10大html5前端框架
  2. java面向对象三大特性:封装、继承、多态——举例说明
  3. 【粉丝需求】如何把一个前端网页都搞下来?
  4. bzoj2287【POJ Challenge】消失之物 缺一01背包
  5. Shell编程之变量
  6. Linux LVM动态扩容
  7. java 字符串和整型的相互转换
  8. 【android自定义控件】button样式自定义二
  9. 微信小程序实现上传图片的功能
  10. 【嵌入式C语言系列】关键字详解【const】
  11. 区块链安全入门与实战
  12. 基于Android和Java的校园外卖系统设计与实现
  13. linux 文件名带日期,在linux中追加日期到文件名
  14. RocketMQ可视化Web管理界面
  15. Android 应用开发入门
  16. 成熟港口人工智能Ceaspectus领跑全球智能港口码头人工智能应用落地,全球No.1集装箱AI企业中集飞瞳建设智慧港口智能码头
  17. 二进制与8,10,16转换
  18. 内存调试神器- ASan详解及实例分析
  19. 安卓6.0+关机状态下通电自动开机方案
  20. 工商管理专业考计算机二级,全国计算机二级科目怎么选

热门文章

  1. 嵌入式分享合集126
  2. 用Python做一个游戏辅助脚本,完整编程思路分享
  3. TCP/IP Attack Lab(SEED实验)
  4. 新手网站制作教程:网站建设流程及步骤有哪些?
  5. linux 硬盘合并使用方法,Linux硬盘合并的实现代码
  6. java中display中的属性_全面解析display属性
  7. 洛谷 P3604 美好的每一天
  8. 小米手机销量超过苹果晋升全球第二
  9. 大气科学领域必备的模型软件汇总丨WRF、WRF-CMAQ、WRF-Chem、WRF-Hydro、WRF DA、PMF、MCM、CAMx、SMOKE、CMIP6等
  10. maven dependency 警告:Overriding managed version XXX for XXX