赞
踩
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接受多个键作为参数,如果任何一个键发生变化,它会取消现有的协程并重新启动。这对于执行副作用非常有用,例如进行网络调用或更新数据库,而不会阻塞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
只有在文本值更改时才会增加,因此此函数是无副作用的。
为确保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
函数来轻松且安全地管理异步操作。
当你想在 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 组合函数用于在 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 变量:data
和 disposableEffect
。
在 DisposableEffect 中,我们调用了一个异步操作 someAsyncOperation()
,它返回一个 Observable
,在操作完成时会发出一个新值。我们订阅它并更新 data
。
我们还使用onDispose
来处理 disposable 和停止操作,当 Composable 被移除时会被自动调用。
最后,我们将 disposableEffect
设置为 disposable
对象,以便调用 Composable 可以访问它。
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
将非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
的示例:
@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函数,其中包含一个可变的状态变量firstName
和lastName
。我们使用snapshotFlow
创建一个新的流变量fullName
,它将firstName
和lastName
的值连接起来。每当firstName或lastName发生更改时,snapshotFlow
重新组合Composable并更新fullName
。最后,我们使用Text Composable显示fullName
。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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。