Android Compose 中 Side Effects 和 State 相关的 API 使用

Android Compose 中 Side Effects 和 State 相关的 API 使用

文章目录

  • [Android Compose 中 Side Effects 和 State 相关的 API 使用](#Android Compose 中 Side Effects 和 State 相关的 API 使用)
    • [Side Effects (副作用)](#Side Effects (副作用))
      • [1. LaunchedEffect](#1. LaunchedEffect)
      • [2. rememberCoroutineScope](#2. rememberCoroutineScope)
      • [3. rememberUpdatedState](#3. rememberUpdatedState)
      • [4. DisposableEffect](#4. DisposableEffect)
      • [5. produceState](#5. produceState)
      • [6. remember (与 Side Effects 的关联)](#6. remember (与 Side Effects 的关联))
    • State (状态)
      • [1. remember 和 mutableStateOf](#1. remember 和 mutableStateOf)
      • [2. rememberSaveable](#2. rememberSaveable)
      • [3. State 和 MutableState 接口](#3. State 和 MutableState 接口)
      • [4. derivedStateOf](#4. derivedStateOf)
      • [5. snapshotFlow](#5. snapshotFlow)
    • 总结

本文首发地址 https://h89.cn/archives/385.html

很高兴能和你一起深入探讨 Android Compose 中 Side Effects 和 State 相关的 API。理解这些概念对于构建健壮且行为正确的 Compose 应用至关重要。让我们一起细致地了解它们的使用方式。

Side Effects (副作用)

在 Compose 的世界里,Side Effects 指的是在 Composable 函数之外发生的操作。由于 Composable 函数应该具有幂等性(多次执行产生相同的结果)且无副作用,因此我们需要特定的 API 来安全地执行这些操作。

1. LaunchedEffect

LaunchedEffect 用于在 Composable 的生命周期内启动一个协程。当 Composable 首次进入组合时,协程启动,当 Composable 离开组合时,协程取消。如果 LaunchedEffect 的 key 发生变化,现有的协程会被取消,并启动一个新的协程。

kotlin 复制代码
import androidx.compose.runtime.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun MyComposable(data: String) {
    val snackbarHostState = remember { SnackbarHostState() }

    LaunchedEffect(data) { // key 是 data
        snackbarHostState.showSnackbar("Data changed: $data")
        delay(3000) // 模拟一个耗时操作
        println("Snackbar shown for $data")
    }

    // ... UI 元素,例如 SnackbarHost
}

使用场景:

  • 执行一次性的异步操作,例如网络请求、数据库操作等。
  • 在特定的状态变化时触发某些操作。
  • 与生命周期相关的操作,例如在 Composable 显示时启动动画,隐藏时停止动画。

关键点:

  • 需要一个或多个 key 参数。当这些 key 的值发生变化时,会重新启动协程。如果不希望重新启动,可以使用 Unit 作为 key。
  • 在协程内部可以使用 suspend 函数。
  • Composable 离开组合时,协程会被自动取消,避免内存泄漏。

2. rememberCoroutineScope

rememberCoroutineScope 用于获取一个与 Composable 的生命周期绑定的 CoroutineScope。你可以在这个 scope 中启动多个协程,并且当 Composable 离开组合时,这些协程都会被取消。

kotlin 复制代码
import androidx.compose.runtime.*
import kotlinx.coroutines.launch

@Composable
fun MyScreen() {
    val coroutineScope = rememberCoroutineScope()
    val scaffoldState = rememberScaffoldState()

    Scaffold(scaffoldState = scaffoldState) {
        Button(onClick = {
            coroutineScope.launch {
                scaffoldState.snackbarHostState.showSnackbar("Button clicked!")
            }
        }) {
            Text("Click Me")
        }
    }
}

使用场景:

  • 在 Composable 内部响应用户事件或其他事件时启动多个相关的协程。
  • 需要对一组相关的异步任务进行统一的生命周期管理。

关键点:

  • 返回的 CoroutineScope 会在 Composable 离开组合时自动取消其所有子协程。
  • 适用于需要在 Composable 的作用域内进行更细粒度协程管理的场景。

3. rememberUpdatedState

rememberUpdatedState 用于在 Side Effect 中引用 Composable 的最新状态。当 Composable 重新组合时,Side Effect 中捕获的状态可能已经过时。rememberUpdatedState 返回一个 State 对象,该对象会随着 Composable 的重新组合而更新。

kotlin 复制代码
import androidx.compose.runtime.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun MyDelayedOperation(onTimeout: () -> Unit) {
    val updatedOnTimeout = rememberUpdatedState(newValue = onTimeout)

    LaunchedEffect(Unit) {
        delay(5000)
        updatedOnTimeout.value() // 调用的是最新的 onTimeout
    }

    Text("Operation will timeout in 5 seconds")
}

// 父 Composable
@Composable
fun ParentComposable() {
    var counter by remember { mutableStateOf(0) }

    MyDelayedOperation(onTimeout = {
        println("Timeout! Counter: $counter")
        counter++
    })

    Button(onClick = { counter++ }) {
        Text("Increment Counter")
    }
}

使用场景:

  • 当 Side Effect 需要引用一个可能在 Composable 生命周期内发生变化的状态时,例如回调函数。
  • 确保 Side Effect 中使用的闭包捕获的是最新的状态值。

关键点:

  • 返回的是一个 State 对象,需要通过 .value 访问最新的值。
  • 避免了在 Side Effect 中使用过时的闭包变量。

4. DisposableEffect

DisposableEffect 用于在 Composable 进入组合和离开组合时执行特定的操作。它提供了一个 onDispose 块,在该块中可以执行清理操作,例如取消订阅、释放资源等。

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver

@Composable
fun LifecycleObserverComposable(onStart: () -> Unit, onStop: () -> Unit) {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> onStart()
                Lifecycle.Event.ON_STOP -> onStop()
                else -> Unit
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        // onDispose 块,在 Composable 离开组合时执行
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    Text("Observing lifecycle events")
}

// 使用示例
@Composable
fun MyScreenWithLifecycle() {
    LifecycleObserverComposable(
        onStart = { println("Composable started") },
        onStop = { println("Composable stopped") }
    )
}

使用场景:

  • 订阅和取消订阅外部资源,例如 Lifecycle 事件、传感器、广播接收器等。
  • 执行需要在 Composable 不再显示时进行清理的操作。

关键点:

  • 需要一个或多个 key 参数。当这些 key 的值发生变化时,会先执行 onDispose 中的清理操作,然后再执行新的 Effect。
  • onDispose 块是确保资源正确释放的关键。

5. produceState

produceState 用于在 Compose 的 State 系统之外异步地生成 State。它接收一个初始值和一个协程块,该协程块可以更新返回的 MutableState。当 Composable 离开组合时,生产协程会被取消。

kotlin 复制代码
import androidx.compose.runtime.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

@Composable
fun RandomNumberProducer(): State<Int> {
    return produceState(initialValue = 0) {
        while (true) {
            delay(1000)
            val randomNumber = (0..100).random()
            value = randomNumber // 更新 State 的值
        }
    }
}

@Composable
fun DisplayRandomNumber() {
    val randomNumberState = RandomNumberProducer()
    Text("Random Number: ${randomNumberState.value}")
}

// 使用 Flow 生成 State
@Composable
fun NumberFlowProducer(numberFlow: Flow<Int>): State<Int> {
    return produceState(initialValue = 0, numberFlow) {
        numberFlow.collect { value = it }
    }
}

使用场景:

  • 将基于回调或其他异步模型的外部数据源转换为 Compose 的 State。
  • 从 Flow 或其他异步数据流中生成 State。

关键点:

  • 返回一个 State 对象,可以直接在 Composable 中使用。
  • 生产协程的生命周期与 Composable 的生命周期绑定。

6. remember (与 Side Effects 的关联)

虽然 remember 本身不是专门用于 Side Effects 的 API,但它经常与 Side Effects 结合使用。remember 用于在 Composable 的重组之间保留状态。这对于创建只需要初始化一次的 Side Effect 相关对象非常有用。

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.material.SnackbarHostState

@Composable
fun MyComposableWithSnackbar() {
    val snackbarHostState = remember { SnackbarHostState() } // 只会创建一次 SnackbarHostState

    // ... 使用 snackbarHostState 的 Side Effect (例如 LaunchedEffect)
}

使用场景:

  • 创建只需要初始化一次的 Side Effect 管理器或状态持有者。

关键点:

  • remember 的返回值在 Composable 的整个生命周期内保持不变,除非 key 发生变化(如果提供了 key)。

State (状态)

在 Compose 中,State 是指可以随时间变化的数据。Compose 的核心思想是根据 State 的变化来更新 UI。

1. remember 和 mutableStateOf

remember 是一个 Composable 函数,用于在重组之间保留状态。mutableStateOf 是一个工厂函数,用于创建一个可观察的 MutableState 对象。当 MutableState 的 value 属性发生变化时,Compose 会安排重组任何读取该 State 的 Composable。

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.material.Button
import androidx.compose.material.Text

@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) } // 使用属性委托简化 State 的读写

    Button(onClick = { count++ }) {
        Text("Increment: $count")
    }
}

使用场景:

  • 存储 Composable 内部需要修改和观察的简单状态。

关键点:

  • remember 确保状态在重组之间不会丢失。
  • mutableStateOf 创建一个可观察的状态容器。
  • 可以使用属性委托 by 来更简洁地读写 MutableState 的 value。

2. rememberSaveable

rememberSaveable 的作用与 remember 类似,但它还可以在 Activity 或进程重新创建后恢复状态。它适用于需要跨配置更改(例如屏幕旋转)或进程终止保留的状态。

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.material.Text
import androidx.compose.material.TextField

@Composable
fun NameInput() {
    var name by rememberSaveable { mutableStateOf("") }

    TextField(
        value = name,
        onValueChange = { name = it },
        label = { Text("Enter your name") }
    )
    Text("Hello, $name!")
}

使用场景:

  • 存储用户输入、UI 状态等需要在配置更改后保留的数据。

关键点:

  • 内部使用 Bundle 来保存和恢复状态。
  • 只有可以存储在 Bundle 中的数据类型才能被 rememberSaveable 正确保存。对于更复杂的数据类型,需要提供自定义的 Saver 对象。

3. State 和 MutableState 接口

State 是一个只读的接口,表示一个可以被观察的状态值。它只有一个 value 属性。MutableState 继承自 State,并添加了一个可写的 value 属性,当该属性被修改时,会通知观察者(Compose 运行时)。

kotlin 复制代码
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.material.Text
import androidx.compose.runtime.Composable

@Composable
fun MyStatefulComposable() {
    val myState: MutableState<String> = remember { mutableStateOf("Initial Value") }
    val readOnlyState: State<String> = myState

    Button(onClick = { myState.value = "Updated Value" }) {
        Text("Update State")
    }
    Text("Current State: ${readOnlyState.value}")
}

使用场景:

  • 作为状态 holders 或 ViewModels 中公开状态的类型。
  • 在 Composable 内部管理状态。

关键点:

  • State 提供只读访问,确保状态只能通过特定的方式修改。
  • MutableState 提供读写访问,并触发 UI 的重组。

4. derivedStateOf

derivedStateOf 用于根据一个或多个现有的 State 计算出一个新的 State。当原始 State 发生变化时,derivedStateOf 创建的 State 会自动重新计算。这有助于避免在重组时进行不必要的计算。

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.material.Text

@Composable
fun UserList(users: List<String>) {
    val userCount by remember { derivedStateOf { users.size } }
    Text("Number of users: $userCount")

    // ... 显示用户列表
}

// 父 Composable
@Composable
fun ParentUserList() {
    var userList by remember { mutableStateOf(listOf("Alice", "Bob")) }
    Button(onClick = { userList = userList + "Charlie" }) {
        Text("Add User")
    }
    UserList(users = userList)
}

使用场景:

  • 根据其他 State 的值派生出新的 UI 状态。
  • 优化性能,避免在原始 State 没有影响到派生 State 时进行不必要的重组。

关键点:

  • 只有当派生 State 的计算结果发生变化时,才会触发依赖于它的 Composable 的重组。

5. snapshotFlow

snapshotFlow 用于将 Compose 的 State 转换为 Kotlin 的 Flow。这允许你使用 Flow 的强大操作符来处理 Compose 的状态变化。

kotlin 复制代码
import androidx.compose.runtime.*
import kotlinx.coroutines.flow.*

@Composable
fun SearchBar() {
    var query by remember { mutableStateOf("") }
    val searchResults = remember {
        snapshotFlow { query }
            .debounce(300)
            .mapLatest { performSearch(it) } // 假设 performSearch 是一个挂起函数
            .collectAsState(initial = emptyList())
    }

    TextField(
        value = query,
        onValueChange = { query = it },
        label = { Text("Search") }
    )

    // 显示 searchResults
}

suspend fun performSearch(query: String): List<String> {
    delay(500) // 模拟搜索延迟
    return listOf("Result 1 for $query", "Result 2 for $query")
}

使用场景:

  • 将 Compose 的 State 集成到基于 Flow 的数据流中,以便进行更复杂的异步处理。
  • 使用 Flow 的操作符(例如 debounce、map、filter)来处理状态变化。

关键点:

  • 返回一个 Flow,该 Flow 会在每次 Compose 运行时捕获 State 的快照。
  • 需要使用 collectAsState() 将 Flow 转换回 Compose 的 State 以在 UI 中使用。

总结

理解 Side Effects 和 State 是构建复杂且响应迅速的 Android Compose 应用的关键。

  • Side Effects 允许你在 Composable 的生命周期内安全地执行 Composable 范围之外的操作,并提供了各种 API 来处理不同类型的副作用,例如一次性操作 (LaunchedEffect)、生命周期绑定的协程 (rememberCoroutineScope)、引用最新状态 (rememberUpdatedState)、资源清理 (DisposableEffect) 和异步状态生产 (produceState).
  • State 是驱动 UI 更新的数据,Compose 提供了多种 API 来管理不同生命周期和复杂度的状态,包括简单的内部状态 (remember 和 mutableStateOf)、可持久化状态 (rememberSaveable)、只读和可写状态接口 (State 和 MutableState)、派生状态 (derivedStateOf) 以及将 State 转换为 Flow 的机制 (snapshotFlow).

通过合理地使用这些 API,你可以构建出既高效又易于维护的 Compose 应用。记住,Composable 函数本身应该是无副作用的,所有与外部世界的交互都应该通过 Side Effects 来处理,而 UI 的更新则应该通过 State 的变化来驱动。

希望这个详细的介绍能够帮助你更好地理解和使用 Android Compose 中的 Side Effects 和 State 相关的 API!如果你有任何更深入的问题或者想了解特定的使用场景,欢迎随时提出。


本文来自 Gemini 总结


相关推荐
警醒与鞭策18 小时前
【无标题】
android·unity·性能优化·游戏引擎·perforce
传奇开心果编程19 小时前
【Jetpack Compose进阶学与练】第14课:系列收尾复习总结;Compose项目常见坑点汇总;学习路线与后续学习方向
android·学习·ui·kotlin·android jetpack
李游Leo1 天前
《HarmonyOS 7 精准碰一碰跨设备协作开发实战》07:异常恢复、状态机与CrossDrop工程收尾【鸿蒙心迹】
android·harmonyos
方白羽1 天前
Android APK安全防护
android·安全·apk
小黄人软件1 天前
AndroidStudio老项目 macOS运行BlueTooth蓝牙串口助手(Android+Studio源码).rar
android·macos
00后程序员张1 天前
苹果App Store上架指南:费用、原理与步骤详解
android·ios·小程序·https·uni-app·iphone·webview
天神哥哥啊1 天前
cocos联调注意事项-安卓
android
TDengine (老段)1 天前
TDengine TSDB 实战排障四(升级与兼容)
android·java·大数据·数据库·物联网·时序数据库·tdengine
蓝速科技1 天前
政务自助终端信创选型与无人值守落地方案
android·大数据·数据库·人工智能·科技·技术分享·政务
硫酸锌011 天前
在手机上运行Python增量备份手机数据到PC电脑
android·windows·python