百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Compose基础-Side-effect(二)

nanshan 2024-11-20 19:24 20 浏览 0 评论

1. 前言

Compose基础-Side-effect(一)中,我们学习了几个常用的Side-effect:LaunchedEffectrememberCoroutineScope,以及关键字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 event
  onStop: () -> Unit   // Send the 'stopped' analytics event
) {
    // Safely update the current lambdas when a new one is provided
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    // If `lifecycleOwner` changes, dispose and reset the effect
    DisposableEffect(lifecycleOwner) {
        // Create an observer that triggers our remembered callbacks
        // for sending analytics events
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                currentOnStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                currentOnStop()
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            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 attached
    SideEffect {
        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 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)
        }
    }
}

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(https://developer.android.com/jetpack/compose/side-effects)

Tricky refactoring of Jetpack Compose code — be careful with side effects(https://blog.thefuntasty.com/tricky-refactoring-of-jetpack-compose-code-be-careful-with-side-effects-13768275b3db)

相关推荐

如何为MySQL服务器和客户机启用SSL?

用户想要与MySQL服务器建立一条安全连接时,常常依赖VPN隧道或SSH隧道。不过,获得MySQL连接的另一个办法是,启用MySQL服务器上的SSL封装器(SSLwrapper)。这每一种方法各有其...

Mysql5.7 出现大量 unauthenticated user

线上环境mysql5.7突然出现大量unauthenticateduser,进mysql,showprocesslist;解决办法有:在/etc/hosts中添加客户端ip,如192.16...

MySQL 在 Windows 系统下的安装(mysql安装教程windows)

更多技术文章MySQL在Windows系统下的安装1.下载mysql和Framework链接链接:百度网盘请输入提取码提取码:6w3p双击mysql-installer-communit...

MySql5.7.21.zip绿色版安装(mysql数据库绿色版安装)

1、去网上下载满足系统要求的版本(mysql-5.7.21-winx64.zip)2、直接解压3、mysql的初始化(1)以管理员身份运行cmd,在mysql中的bin目录下shift+右键-在...

MySQL(8.0)中文全文检索 (亲测有效)

在一堆文字中找到含有关键字的应用。当然也可以用以下语句实现:SELECT*FROM<表名>WHERE<字段名>like‘%ABC%’但是它的效率太低,是全盘扫描。...

新手教程,Linux系统下MySQL的安装

看了两三个教程。终于在哔哩哔哩找到一个简单高效的教程,成功安装,up主名叫bili逍遥bili,感兴趣可以去看看。下面这个是我总结的安装方法环境:CentOS764位1.下载安装包,个人觉得在...

麒麟服务器操作系统安装 MySQL 8 实战指南

原文连接:「链接」Hello,大家好啊,今天给大家带来一篇麒麟服务器操作系统上安装MySQL8的文章,欢迎大家分享点赞,点个在看和关注吧!MySQL作为主流开源数据库之一,被广泛应用于各种业务...

用Python玩转MySQL的全攻略,从环境搭建到项目实战全解析

这是一篇关于“MySQL数据库入门实战-Python版”的教程,结合了案例实战分析,帮助初学者快速掌握如何使用Python操作MySQL数据库。一、环境准备1.安装Python访问Pytho...

安装MySQL(中标麒麟 安装mysql)

安装MySQL注意:一定要用root用户操作如下步骤;先卸载MySQL再安装1.安装包准备(1)查看MySQL是否安装rpm-qa|grepmysql(2)如果安装了MySQL,就先卸载rpm-...

Mysql最全笔记,快速入门,干货满满,爆肝

目录一、MySQL的重要性二、MySQL介绍三、软件的服务架构四、MySQL的安装五、SQL语句六、数据库相关(DDL)七、表相关八、DML相关(表中数据)九、DQL(重点)十、数据完...

MAC电脑安装MySQL操作步骤(mac安装mysqldb)

1、在官网下载MySQL:https://dev.mysql.com/downloads/mysql/根据自己的macOS版本,选择适配的MySQL版本根据自己需求选择相应的安装包,我这里选择macO...

mysql主从(mysql主从切换)

1、本章面试题什么是mysql主从,主从有什么好处什么是读写分离,有什么好处,使用mycat如何实现2、知识点2.1、课程回顾dubboORM->MVC->RPC->SOApro...

【linux学习】以MySQL为例,带你了解数据库

做运维的小伙伴在日常工作中难免需要接触到数据库,不管是MySQL,mariadb,达梦还是瀚高等其实命令都差不多,下面我就以MySQL为例带大家一起来了解下数据库。有兴趣的小伙伴不妨评论区一起交流下...

玩玩WordPress - 环境简介(0)(玩玩网络科技有限公司)

简介提到开源博客系统,一般都会直接想到WordPress!WordPress是使用PHP开发的,数据库使用的是MySQL,一般会在Linux上运行,Nginx作为前端。这时候就需要有一套LNMP(Li...

服务器常用端口都有哪些?(服务器端使用的端口号范围)

下面为大家介绍一下,服务器常用的一些默认端口,以及他们的作用:  21:FTP服务所开放的端口,用于上传、下载文件。  22:SSH端口,用于通过命令行模式远程连接Linux服务器或vps。  23:...

取消回复欢迎 发表评论: