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

本文首发地址 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 发生变化,现有的协程会被取消,并启动一个新的协程。

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 离开组合时,这些协程都会被取消。

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 的重新组合而更新。

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 块,在该块中可以执行清理操作,例如取消订阅、释放资源等。

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 离开组合时,生产协程会被取消。

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 相关对象非常有用。

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 对象。当 MutableStatevalue 属性发生变化时,Compose 会安排重组任何读取该 State 的 Composable。

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 来更简洁地读写 MutableStatevalue

2. rememberSaveable

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

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 运行时)。

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 会自动重新计算。这有助于避免在重组时进行不必要的计算。

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 的状态变化。

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 的操作符(例如 debouncemapfilter)来处理状态变化。

关键点:

  • 返回一个 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 来管理不同生命周期和复杂度的状态,包括简单的内部状态 (remembermutableStateOf)、可持久化状态 (rememberSaveable)、只读和可写状态接口 (StateMutableState)、派生状态 (derivedStateOf) 以及将 State 转换为 Flow 的机制 (snapshotFlow).

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

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


本文来自 Gemini 总结



本文链接:Android Compose 中 Side Effects 和 State 相关的 API 使用 - https://h89.cn/archives/385.html

版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。

标签: compose, LaunchedEffect, rememberCoroutineScope, rememberUpdatedState, DisposableEffect, produceState, derivedStateOf, Side Effects, State, MutableState

添加新评论