Android Jetpack Compose的附带效应

Android Jetpack Compose的附带效应

Compose side-effect
Jetpack Compose 是许多开发人员的首选,因为它具有有趣、简单、有效和直接的特性,并且能够轻松地以声明方式构建自定义组件。但是,要充分利用其功能,重要的是要很好地掌握副作用和效果处理程序。

什么是附带效应?

在 Android 中构建 UI 时,管理副作用可能是开发人员面临的最大挑战之一。它是在可组合函数范围之外发生的应用程序状态更改。

// Side Effect
private var i = 0

@Composable
fun SideEffect() {
    
    
    var text by remember {
    
    
        mutableStateOF("")
    }
    Column {
    
    
        Button(onClick = {
    
     text += "@" }) {
    
    
            i++
            Text(text)
        }
    }
}

在这个例子中,SideEffect使用mutableStateOf创建一个可变状态对象,初始值为空字符串。现在在按钮单击时,我们正在更新文本,并且在文本更新时,我们想要更新i的值。但是Button组合可能会在没有点击的情况下重新组合,这不会改变文本,但会增加i的值。如果这是一个网络调用,那么每次Button重新组合时都会进行网络调用。

理想情况下,您的组合应该是无副作用的,但有时您需要副作用,例如触发一次性事件,例如进行网络调用或收集流。

为解决这些问题,Compose提供了各种用于不同情况的副作用,包括以下内容:

LaunchedEffect

LaunchedEffect是一个可组合函数,用于在组合范围内启动协程。当LaunchedEffect进入组合时,它会启动一个协程,在离开组合时取消它。LaunchedEffect接受多个键作为参数,如果任何一个键发生变化,它会取消现有的协程并重新启动。这对于执行副作用非常有用,例如进行网络调用或更新数据库,而不会阻塞UI线程。

// Launched Effect
private var i = 0

@Composable
fun SideEffect() {
    
    
    var text by remember {
    
    
        mutableStateOF("")
    }
    
    LaunchedEffect(key1 = text) {
    
    
        i++
    }
    Column {
    
    
        Button(onClick = {
    
     text += "@" }) {
    
    
            Text(text)
        }
    }
}

在上面的示例中,每次文本更新时,都会启动一个新的协程并相应地更新i的值。由于i只有在文本值更改时才会增加,因此此函数是无副作用的。

rememberCoroutineScope

为确保LaunchedEffect在第一次组合时启动,请按原样使用它。但是,如果需要手动控制启动,请改用rememberCoroutineScope。可以使用它来获取一个与组合点绑定的协程作用域,以在组合外部启动协程。rememberCoroutineScope是一个可组合的函数,它返回一个协程作用域,该作用域绑定到调用它的Composable的位置。当调用离开组合时,该作用域将被取消。

@Composable
fun MyComponent() {
    
    
    val coroutineScope = rememberCoroutineScope()
    val data = remember {
    
     mutableStateOf("") }

    Button(onClick = {
    
    
        coroutineScope.launch {
    
    
            // Simulate network call
            delay(2000)
            data.value = "Data loaded"
        }
    }) {
    
    
        Text("Load data")
    }

    Text(text = data.value)
}

在这个例子中,rememberCoroutineScope被用来创建一个与Composable函数的生命周期绑定的协程作用域。这样做可以通过确保当Composable函数从组合中删除时取消协程来高效且安全地管理协程。您可以在此范围内使用launch函数来轻松且安全地管理异步操作。

rememberUpdatedState

当你想在 effect 中引用一个值时,如果该值在改变时不应该重启,则可以使用 rememberUpdatedState。LaunchedEffect 会在 key 参数的任一值更新时重新启动,但有时我们希望在 effect 内部捕获更改的值而不重新启动它。如果我们有一个长时间运行的选项,重新启动会非常昂贵,这种处理方式就非常有帮助。

@Composable
fun ParentComponent() {
    
    
     setContent {
    
    
            ComposeTheme {
    
    
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
    
    
                    var dynamicData by remember {
    
    
                        mutableStateOf("")
                    }
                    LaunchedEffect(Unit) {
    
    
                        delay(3000L)
                        dynamicData = "New Text"
                    }
                    MyComponent(title = dynamicData)
                }
            }
        }
}

@Composable
fun MyComponent(title: String) {
    
    
  var data by remember {
    
     mutableStateOf("") }

    val updatedData by rememberUpdatedState(title)

    LaunchedEffect(Unit) {
    
    
        delay(5000L)
        data = updatedData
    }

    Text(text = data)
}

初始情况下,title 是一个空字符串。在 3 秒后,title 变成了“New Text”。在 5 秒后,data 也变成了“New Text”,从而触发了 UI 的重新组合。这更新了 Text 组合。因此,总延迟时间为 5 秒,如果我们没有使用 rememberUpdatedState,那么我们必须重新启动第二个 LaunchedEffect,这将需要 8 秒。

DisposableEffect

DisposableEffect 组合函数用于在 Composable 函数最初创建时执行一个效果。当 Composable 从屏幕中移除时,它会清除效果。

@Composable
fun MyComponent() {
    
    
    var data by remember {
    
     mutableStateOf("") }
    val disposableEffect = remember {
    
     mutableStateOf<Disposable?>(null) }

    DisposableEffect(Unit) {
    
    
        val disposable = someAsyncOperation().subscribe {
    
    
            data = it
        }
        onDispose {
    
    
            disposable.dispose()
        }
        disposableEffect.value = disposable
    }

    // rest of the composable function
}

在这个例子中,我们创建了一个名为 MyComponent 的 Composable。它有两个 mutable state 变量:datadisposableEffect

在 DisposableEffect 中,我们调用了一个异步操作 someAsyncOperation(),它返回一个 Observable,在操作完成时会发出一个新值。我们订阅它并更新 data

我们还使用onDispose来处理 disposable 和停止操作,当 Composable 被移除时会被自动调用。

最后,我们将 disposableEffect 设置为 disposable 对象,以便调用 Composable 可以访问它。

SideEffect

SideEffect 用于将 Compose 状态发布给非 Compose 代码。SideEffect 在每次重新组合时触发,它不是一个协程作用域,因此不能在其中使用挂起函数。

当我最初发现这个副作用时,我对它的重要性和重要性的程度感到不确定,因此我更深入地研究了这个问题以获得更好的理解。

class Ref(var value: Int)

@Composable
inline fun LogCompositions(tag: String) {
    
    
        val ref = remember {
    
     Ref(0) }
        SideEffect {
    
     ref.value++ }
        Logger.log("$tag Compositions: ${
      
      ref.value}")
}

当effect被调用时,它会记录创建的composition数量。

produceState

produceState将非compose状态转换为compose状态。它启动一个协程,作用域是composition,可以将值推入返回状态中。当produceState进入Composition时,生产者启动;当它离开Composition时停止。返回的State组合在一起;设置相同的值不会导致重组。

下面是如何使用produceState从网络加载图像的示例。loadNetworkImage composable函数提供了可在其他composable中使用的State。此示例选自官方文档。

@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: 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)
        }
    }
}

derivedStateOf

derivedStateOf是一个可以用来基于其他状态变量的值派生新状态的可组合函数。当需要计算一个依赖于其他值的值时,并且希望避免不必要的重新计算,它就非常有用。

下面是一个使用derivedStateOf的示例:

@Composable
fun MyComponent() {
    
    
    var firstName by remember {
    
     mutableStateOf("") }
    var lastName by remember {
    
     mutableStateOf("") }

    val fullName = derivedStateOf {
    
    
        "$firstName $lastName"
    }

    Text(text = "Full Name: $fullName")
}

在这个例子中,我们定义了一个名为MyComponent的Composable函数,其中包含一个可变的状态变量firstNamelastName。我们使用snapshotFlow创建一个新的流变量fullName,它将firstNamelastName的值连接起来。每当firstName或lastName发生更改时,snapshotFlow重新组合Composable并更新fullName。最后,我们使用Text Composable显示fullNamesnapshotFlow可用于创建流,以响应状态的更改,而无需手动管理回调或侦听器。

snapshotFlow

snapshotFlow是一个函数,可以用于创建一个流(Flow),该流会首先发出状态对象的当前值,然后发出任何后续更改。这对于创建响应式 UI 非常有用,可以响应状态的更改,而无需手动管理回调或侦听器。

以下是在Compose中使用snapshotFlow的示例:

@Composable
fun MyComponent() {
    
    
    val count = remember {
    
     mutableStateOf(0) }

    val countFlow = snapshotFlow {
    
     count.value }

    LaunchedEffect(countFlow) {
    
    
        countFlow.collect {
    
     value ->
            // Handle the new value
        }
    }

    Button(onClick = {
    
     count.value++ }) {
    
    
        Text("Clicked ${
      
      count.value} times")
    }

在这个例子中,MyComponent使用mutableStateOf(0)创建了一个可变状态对象。然后调用snapshotFlow,传入一个lambda表达式返回状态对象的当前值。由此产生的countFlow流会发射当前值和状态对象的任何后续更改。

使用LaunchedEffect来从countFlow流中收集数据,确保只有在组件处于活动状态时才进行收集,并在移除时停止。最后,使用Button来更新状态对象的值。

参考

https://proandroiddev.com/mastering-side-effects-in-jetpack-compose-b7ee46162c01

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/130558254