Jetpack Compose — 让Composable具备生命周期感知

news2024/11/17 15:27:13

Jetpack Compose — 让Composable具备生命周期感知

我们将研究不同的方法来实现可组合(Composable)的生命周期感知。我们还将了解可组合生命周期和视图(View)生命周期之间的区别。

我们将逐步探索不同的解决方案,以寻找一种更好的方式来观察“Jetpack Compose-Way”中组件生命周期事件。

Composable的生命周期是什么?

在官方文档中已经清楚地解释了Composable的生命周期。在本文中,我将简要介绍一下。

https://developer.android.com/jetpack/compose/lifecycle

组合的 lifecycle由以下阶段定义:

Enter the Composition - 当Jetpack Compose第一次运行组合时,它会跟踪用于描述UI的组合,并构建所有组合的树形结构,称为组合。
Recomposition - 当任何状态发生变化最终影响UI时,Jetpack Compose聪明地识别出这些组合,并仅对它们进行重新组合,而无需更新所有组合。
Leave the Composition - 当UI不再可见时,它是最后一个阶段,因此会删除所有已使用的资源。
以下图表(源自官方文档)很好地展示了这些阶段。

https://developer.android.com/jetpack/compose/lifecycle

View的生命周期是什么?

在移动开发中,视图的生命周期是一个非常基本的概念,它是UI层许多功能依赖的核心模式。通过控制视图的不同状态,我们可以执行所需的工作。这些不同的状态包括onCreate、onStart、onPause、onResume、onStoponDestroy

在不同的使用情境下,我们必须对这些生命周期事件做出相应的反应。例如,如果用户离开页面,可能有一些资源不再需要,我们可以释放它们;或者如果用户从后台返回到前台,可能希望重新获取最新的信息以展示更新的内容等等。这样的使用情境还有很多。

Composable的 生命周期 vs View的生命周期

可组合(Composable)的生命周期与视图(View)的生命周期是两种不同的模式。

Jetpack Compose 引入了可组合的生命周期,与视图的生命周期无关。可组合的生命周期涉及创建 UI 组件树结构、跟踪状态变化并提供高效的 UI 更新。而视图的生命周期则与用户在我们的应用程序/屏幕中的交互方式触发的事件有关,例如切换到另一个屏幕、切换到后台、切换到前台等。

为了满足许多用例,我们仍然需要使我们的可组合具有生命周期感知的能力。这意味着我们必须监听视图的生命周期事件并对其做出相应的反应,以提供更好的用户体验。

用例

当用户从后台切换到前台时,我们希望重新获取我们应用程序的数据,从后端获取最新信息并使用该信息更新用户界面。

首先,让我们看一下未实现此行为时的代码样式。

// MainViewModel
class NewsViewModel (
    private val newsRepository: NewsRepository = NewsRepositoryImpl()
) : ViewModel() {

    init {
        fetchNews()
    }

    private fun fetchNews() {
        viewModelScope.launch {
            newsRepository.fetchNews()
        }
    }
}

// MainScreen
@Composable
fun NewsScreen(viewModel: NewsViewModel = NewsViewModel()) {
    LazyColumn{
        // showing list of
    }
}

NewsScreen composable 将使用LazyColumn展示一个新闻列表。

我们不会详细讲解 News 部分的 UI 实现,假设它是使用 Jetpack Compose 实现的。

NewsViewModel 在初始化时获取数据,如果用户将应用程序切换到后台,然后再切换到前台,新闻数据将不会再次获取,因为在 onResume 时,viewModelScope 不会自动启动新的协程,fetchNews() 也不会执行。

为了满足这种情况,我们必须使我们的 Composable 感知生命周期,观察生命周期事件,当 onResume 时,我们必须再次获取新闻。

让 Composable具备生命周期感知

每个可组合项都有一个生命周期所有者LocalLifeCycleOwner.current,我们将使用它来为View的生命周期事件添加观察者并对其进行响应。我们还需要确保在View销毁和可组合项离开Composition时移除该观察者。在这里,DisposableEffect副作用API是一个理想选择,它提供了onDispose块进行清理。

如果您不熟悉DisposableEffect API,或者想详细了解,我写了一篇关于DisposableEffect API以及与LaunchedEffectremember(key)的比较的详细故事。您可以从链接中阅读。

下面的代码展示了添加和移除生命周期事件观察者后DisposableEffect API的实现方式。

val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
            val lifecycleEventObserver = LifecycleEventObserver { _, event ->
              // event contains current lifecycle event
            }

            lifecycleOwner.lifecycle.addObserver(lifecycleEventObserver)

            onDispose {
                lifecycleOwner.lifecycle.removeObserver(lifecycleEventObserver)
            }
        }

让我们进一步更新代码,将当前生命周期事件保存到一个状态变量lifecycleEvent中,并扩展之前的示例以响应生命周期事件。

@Composable
fun NewsScreen(
    viewModel: NewsViewModel = NewsViewModel(),
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
    var lifecycleEvent by remember { mutableStateOf(Lifecycle.Event.ON_ANY) }
    DisposableEffect(lifecycleOwner) {
        val lifecycleObserver = LifecycleEventObserver { _, event ->
            lifecycleEvent = event
        }
        
        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        
        onDispose { 
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
    
    LaunchedEffect(lifecycleEvent) {
        if (lifecycleEvent == Lifecycle.Event.ON_RESUME) {
            viewModel.fetchNews()
        }
    }
    
    // will use to display news
    LazyColumn {
        // list of news
    }
}

在上面的代码中,它记住了一个名为lifecycleEvent的状态变量在DisposableEffect内被更新。在NewsScreen组成部分中,添加了一个具有lifecycleEvent作为键的LaunchedEffect,并在lambda内部调用fetchNews,每当lifecycleEvent为ON_RESUME状态时。这将使NewsScreen组成部分具有生命周期感知。 (NewsViewModel的代码将保持不变,即提供fetchNews方法)

现在,每当视图进入恢复状态时,它会再次获取新闻,并且视图会显示最新的内容,实现了我们从后台刷新新闻的用例。

如果有多个需要有生命周期感知的组成部分怎么办?那么让我们将这段代码变得可重用,适用于其他组成部分。

让我们看看下面的可重用代码。

@Composable
fun rememberLifecycleEvent(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current): Lifecycle.Event {
    var lifecycleEvent by remember { mutableStateOf(Lifecycle.Event.ON_ANY) }
    DisposableEffect(lifecycleOwner) {
        val lifecycleObserver = LifecycleEventObserver { _, event ->
            lifecycleEvent = event
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
    return  lifecycleEvent
}

@Composable
fun NewsScreen(viewModel: NewsViewModel = NewsViewModel()) {
    val lifecycleEvent = rememberLifecycleEvent()
    LaunchedEffect(lifecycleEvent) {
        if (lifecycleEvent == Lifecycle.Event.ON_RESUME) {
            viewModel.fetchNews()
        }
    }

    // list of news
    LazyColumn {
        // list of news
    }
}

由于将所有观察生命周期事件的代码移至一个共同的Composable内部,NewsScreen组件内的代码变得更加简洁和易于阅读。内部的Composable会自动记住该特定组件的生命周期状态。NewsScreen只需从rememberLifecycleEvent Composable获取生命周期状态,并将其作为参数传递给LaunchedEffect,以在ON_RESUME时刷新新闻。

然而,这个解决方案存在一个问题:LaunchedEffect不会在ON_CREATE和第一次ON_START生命周期事件触发时执行。它仅从ON_RESUME生命周期事件开始监听。此外,LaunchedEffect适用于与用户界面相关的挂起函数。

一个实际的应用场景是在首次打开任何屏幕时记录分析事件。为了实现这一目标,我们需要在ON_CREATE事件上进行监听以记录分析事件。因此,我们需要找到另一种解决方案以便能够在ON_START / ON_CREATE生命周期事件上做出反应。

为此,我们将使用DisposableEffect API来监听生命周期事件,并在DisposableEffect API的效果块中对其进行响应。我们还希望将该解决方案设计成可复用的,以便能够应用到其他的Composables中。

接下来,让我们来看一下下方的代码示例:

@Composable
fun DisposableEffectWithLifecycle(
    onCreate: () -> Unit = {},
    onStart: () -> Unit = {},
    onStop: () -> Unit = {},
    onResume: () -> Unit = {},
    onPause: () -> Unit = {},
    onDestroy: () -> Unit = {},
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
    val currentOnCreate by rememberUpdatedState(onCreate)
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)
    val currentOnResume by rememberUpdatedState(onResume)
    val currentOnPause by rememberUpdatedState(onPause)
    val currentOnDestroy by rememberUpdatedState(onDestroy)

    DisposableEffect(lifecycleOwner) {
        val lifecycleEventObserver = LifecycleEventObserver { _, event ->
           when (event) {
               Lifecycle.Event.ON_CREATE -> currentOnCreate()
               Lifecycle.Event.ON_START -> currentOnStart()
               Lifecycle.Event.ON_PAUSE -> currentOnPause()
               Lifecycle.Event.ON_RESUME -> currentOnResume()
               Lifecycle.Event.ON_STOP -> currentOnStop()
               Lifecycle.Event.ON_DESTROY -> currentOnDestroy()
               else -> {}
           }
        }
        lifecycleOwner.lifecycle.addObserver(lifecycleEventObserver)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleEventObserver)
        }
    }
}


// News Screen
@Composable
fun NewsScreenWithDisposableEffectLifecycle(viewModel: NewsViewModel = NewsViewModel()) {
    DisposableEffectWithLifecycle(
        onResume = { viewModel.fetchNews() }
    )

    // list of news
    LazyColumn {
        // list of news
    }
}

DisposableEffectWithLifecycle组合函数接受lambda参数来处理所有的生命周期事件,观察并在每个生命周期事件上执行特定方法。DisposableEffectWithLifecycle封装了对生命周期事件的观察,并在离开组合时进行清理。这是一种可重用的解决方案,可轻松集成到其他组合函数中,使其具备生命周期感知的能力。

它解决了我们的问题,并在ON_CREATEON_START时提供事件,而我们之前的解决方案无法做到这一点。

虽然这是一个合理的解决方案,但我们甚至可以更进一步,将这些代码移至ViewModel中,让ViewModel来观察生命周期事件并做出相应的反应。

使ViewModel具备生命周期感知能力

为了使ViewModel能够感知生命周期并监听特定组合项的生命周期事件,我们需要将组合项的生命周期所有者传递给ViewModel。

为此,我们可以为ViewModel编写一个扩展组合项函数。该函数接收组合项的生命周期所有者LocalLifecycleOwner.current.lifecycle,并在onDispose块中添加观察者和移除观察者。ViewModel将实现DefaultLifecycleObserver接口,并开始接收生命周期事件。在OnResume生命周期事件发生时,它将调用fetchNews()方法。

让我们来看一下下面的代码,以了解具体实现。

// Extension function
@Composable
fun <viewModel: LifecycleObserver> viewModel.observeLifecycleEvents(lifecycle: Lifecycle) {
    DisposableEffect(lifecycle) {
        lifecycle.addObserver(this@observeLifecycleEvents)
        onDispose {
            lifecycle.removeObserver(this@observeLifecycleEvents)
        }
    }
}

// ViewModel
class NewsViewModelLifeCycleObserver(
    private val newsRepository: NewsRepository = NewsRepositoryImpl(),
): ViewModel(), DefaultLifecycleObserver {

    override fun onResume(owner: LifecycleOwner) {
        viewModelScope.launch {
            newsRepository.fetchNews()
        }
    }
}

// News Scren 
@Composable
fun NewsScreenWithViewModelAsLifecycleObserver(
    viewModel: NewsViewModelLifeCycleObserver = NewsViewModelLifeCycleObserver()
) {
    viewModel.observeLifecycleEvents(LocalLifecycleOwner.current.lifecycle)

    // list of news
    LazyColumn {
        // list of news
    }
}

ViewModel用于观察事件的变化并做出相应。

业务逻辑已转移到ViewModel中,您可以在特定的生命周期状态下测试ViewModel,并检查该状态下的结果。另外,在UI中我们的代码更简洁,ViewModel中的方法也减少了一个。

结论

  • Compose的生命周期和View的生命周期是两个不同的概念。
  • 每个Compose都有一个生命周期所有者LocalLifecycleOwner.current,我们可以使用它来添加观察器以监听View的生命周期事件。
  • DisposableEffect提供了在onDispose时观察和清理观察器的方式。
  • LaunchedEffect不接收ON_CREATE和第一个ON_START事件。
  • 始终尽量减少UI代码量。

参考

https://developer.android.com/jetpack/compose/lifecycle
https://developer.android.com/reference/android/arch/lifecycle/DefaultLifecycleObserver
https://developer.android.com/jetpack/compose/side-effects#disposableeffect

GitHub

https://github.com/saqib-github-commits/JetpacComposeLifecycleEvents

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

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

相关文章

C51/C52单片机,最小系统

一个小白&#xff0c;开始学习单片机&#xff0c;从C51/52开始&#xff0c; 我学习的型号是STC98C52单片机。 STC89C52是一种低功耗、高性能CMOS8位微控制器&#xff0c;具有8K在系统可编程Flash存储器。在单芯片上&#xff0c;拥有灵巧的8位CPU和在系统可编程Flash&#xff0…

NeRF系列(4):Ha-NeRF: Hallucinated Neural Radiance Fields in the Wild论文解读

主页&#xff1a; 主页&#xff1a;Ha-NeRF&#x1f606;: Hallucinated Neural Radiance Fields in the Wildhttps://rover-xingyu.github.io/Ha-NeRF/论文&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Chen_Hallucinated_Neural_Radiance_Fields_in_…

常用API(Object,Objects,StringBuilder,Math,System,BigDecimal)

1&#xff1a;Object类 1&#xff1a;Object类的作用&#xff1a; 一个类要么默认继承Object类&#xff0c;要么间接继承了Object类&#xff0c;Object类是Java中的祖宗类。Object类的方法是一切子类都可以直接使用的&#xff0c;所以我们要学习Object类的方法。 2&#xff…

容器(第八篇)ansible-模块

ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…

直线方程y=kx+b 已知两点a,b。求ab中间任一点坐标x,y

参考文章 斜率怎么求 已知斜率及另一点坐标,求另一点坐标方法 三角形斜率 DirectX C 3D编程基础 5 [三角形光栅化] 自制中字 直线方程公式ykxb k斜率&#xff0c;b截距 已知点a&#xff08;1,2&#xff09; b&#xff08;4,3&#xff09;绿色线段 求斜率k(by-ay)/(bx-ax)或(a…

idea运行main方法或Test避免编译整个应用的方法

已建项目中修改 Main函数不biuld : 光标点到main函数里&#xff0c;右键->Modify Run COnfigurations->Modify options Test 不build 或者单独在每个test里 右键-Modify Run COnfigurations->移除build 对于新建工程统一修改配置: File->New Project Setting…

Java30天拿下---第一天(JDK,JRE,JVM,转义字符,注释,代码规范,数据类型)

Java开发&#xff08;30天拿下---第一天&#xff09; 一 hello world以及JDK,JRE,JVM二 转义字符三 注释四 代码规范五 DOS命令&#xff08;了解&#xff09;六 变量1.加号的使用2.数据类型整型浮点型字符类型布尔类型自动类型转换强制类型转换String类型 七 API文档 一 hello …

【备战秋招】每日一题:5月13日美团春招第一题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第一题-塔子哥的数字 在线评测链接:P1286 题目描述 塔子哥是一个数学爱好者&#xff0c;他经常思考各种数字之间的奥秘。今天&#xff0c;他想要解决一个有趣的问题&…

业务流程自动化:ThinkAutomation Professional Crack

ThinkAutomation 助力您的业务流程自动化。自动执行本地和基于云的业务流程&#xff0c;以降低成本并节省时间。 自动化传入的通信渠道&#xff0c;监控数据库&#xff0c;对传入的Webhook&#xff0c;Web表单和聊天机器人做出反应。处理文档、附件、本地文件和其他邮件源。 …

TOGAF10®标准中文版(全文目录)

The Open Group是一个通过技术标准实现业务目标的全球联盟。我们拥有超过870个成员组织&#xff0c;成员来自技术社区的各个部门&#xff0c;包括客户、系统和解决方案供应商、工具供应商、集成商和顾问&#xff0c;以及学者和研究人员。 The Open Group的使命是通过以下方式…

33、js - 面试 - 事件循环 微任务 宏任务

1、事件循环♻️&#xff08;EventLoop&#xff09; js是单线程语言&#xff0c;也就是某一刻只能执行一行代码&#xff0c;为了让耗时代码不阻塞其他代码运行&#xff0c;设计了事件循环模型。 事件循环是一个并发模型&#xff0c;负责执行代码、收集异步任务的模型&#xff0…

web性能检测工具lighthouse

About Automated auditing, performance metrics, and best practices for the web. Lighthouse 可以自动检查Web页面的性能。 你可以以多种方式使用它。 浏览器插件 作为浏览器插件&#xff0c;访问chrome网上商店 搜索Lighthouse 插件安装。以两种方式使用。 方式一 安装…

单片机按键软开关:1个引脚实现单片机低电量自己控制给自己断电

背景 类似于手机的开机按键&#xff0c;我们希望在单片机电路上也能实现类似的低电量自动关机、通过按键而不是自锁开关来上电开机。 软开关电路 电路图如下&#xff1a; 当按键按下时&#xff0c;Q1导通&#xff0c;R18右侧变为低电平&#xff0c;进而Q2导通&#xff0c;…

深入探究kubernetes resources - Part 1

在开始使用 Kubernetes 时&#xff0c;社区教给我们的第一件事就是始终为我们 pod 中的每个容器设置 CPU 和内存的请求和限制。 当您指定 Pod 时&#xff0c;您可以选择指定容器需要多少资源。 您指定的最常见资源是 CPU 和内存 (RAM)&#xff1b; apiVersion: v1 kind: Pod …

模糊测试不“模糊”,高效发掘未知漏洞与 0day 攻击

近日&#xff0c;在「DevSecOps软件安全开发实践」课程上&#xff0c;极狐(GitLab) 高级测试工程师衡韬、极狐(GitLab) 高级后端开发工程师田鲁&#xff0c;分享了模糊测试的概念、必要性和在极狐GitLab 上的实践。 以下内容整理自本次直播&#xff0c;你也可以点击&#x1f44…

合宙Air724UG Cat.1模块硬件设计指南--SIM卡接口

SIM卡接口 简介 SIM( Subscriber Identity Module)卡为用户识别模块&#xff0c;内部使用新式单片机及存储器管理结构&#xff0c;包含了大规模的集成电路&#xff0c;同时也称为用户识别卡。通信设备通过SIM卡来识别其用户&#xff0c;只有设备插入SIM卡后才能入网使用。SIM卡…

Javac编译原理:基本结构和工作原理

Javac编译器 文章目录 Javac编译器简介基本结构如何编译程序 工作原理词法分析器语法分析器语义分析器代码生成器 简介 javac是一种编译器&#xff0c;能将一种语言规范转化成另一种语言规范 编译器通常是将便于人理解的语言规范转换成容易理解的语言规范&#xff0c;如C都是…

阿里云服务器价格如何?与其他云服务提供商的价格对比如何?

阿里云服务器价格如何&#xff1f;与其他云服务提供商的价格对比如何&#xff1f;   阿里云服务器价格概述   作为全球领先的云计算服务提供商&#xff0c;阿里云在确保服务器性能和安全性的同时&#xff0c;也非常注重产品的价格竞争力。阿里云服务器&#xff08;ECS&…

OpenMMLab-AI实战营第二期——6-2.玩转AIGC神器MMagic

文章目录 1. 基本介绍2. 动手实验 视频链接&#xff1a;玩转AIGC神器MMagic 代码教程&#xff1a;https://github.com/TommyZihao/MMagic_Tutorials 1. 基本介绍 Stable Diffusion的文生图&#xff0c;以及controlnet和dreamboost的图生图&#xff08;输入一个图像文字描述&am…

python Django web 项目 联通用户管理系统

文章目录 1框架MVC 介绍Django 框架的介绍基础命令Django处理浏览器的请求的流程 部门表部门表显示7.模板的继承部门表的添加部门表的删除request.POST.get(‘key’) 、 request.GET.get(key, )部门表的编辑filter() 得到可迭代的QuerySet对象,支持for循环取容器内的元素first(…