浅谈Jetpack Compose LaunchedEffect
在Jetpack Compose 代码中,我们经常看到LaunchedEffect
这个常见的。今天就简单了解下这个方法吧。在说LaunchedEffect
前,让我们先说一下Side Effect
.
什么是Side Effect
在编程语言中,Side-Effect
指的是调用函数时,除了返回可能的函数值外,该函数还对函数范围外的变量,参数等进行了修改。
举个🌰,看下面的函数,它有2个参数,返回它们的和,但是没有对任何其他变量有修改,所以它是没有副作用(No Side-Effect)的
fun sum(number1: Int, number2: Int) = number1 + number2
但如果我们将函数修改成下面的实现,每次调用该函数都会对sum重新赋值,而sum又是在函数范围外,所以认为这个函数有一个副作用
var sum = 0
fun sum(number1: Int, number2: Int) {
sum = number1 + number2
}
在Jetpack Compose 中,什么是LaunchedEffect
根据源码我们看到,LaunchedEffect
是一个带有@Composable
的函数,注释如下
When LaunchedEffect enters the composition it will launch block into the composition's CoroutineContext. The coroutine will be cancelled and re-launched when LaunchedEffect is recomposed with a different key1 or key2. The coroutine will be cancelled when the LaunchedEffect leaves the composition.
This function should not be used to (re-)launch ongoing tasks in response to callback events by way of storing callback data in MutableState passed to key. Instead, see rememberCoroutineScope to obtain a CoroutineScope that may be used to launch ongoing jobs scoped to the composition in response to event callbacks.
简单来说,LaunchedEffect
是一个可在当前可组合项的作用域内运行挂起函数(block是suspend的)的可组合函数。
在Thinking in Compose中,我们了解到一个可组合项应该是没有副作用的,如果我们想要在可组合项中修改应用状态,需要通过Effect API启动协程操作,所以可以说LaunchedEffect
在Jetpack Compose中提供了在可组合项中调用挂起函数的能力。
下面我们通过一个例子简单了解下, 这个例子是一个TodoTask下的一个小功能,根据选择的类别展示当前类别的所有Task。
简单的准备mock tasks,代码如下:
data class TodoTask(
val id: Long,
val name: String,
val description: String
)
// mock tasks respose
val mockTasks = (1..10).map {
TodoTask(
id = it.toLong(),
name = "task name $it",
description = "task description $it"
)
}
// mock get tasks from api, delay 2 seconds
private suspend fun getTasks(): List<TodoTask> {
delay(2000)
return mockTasks
}
在TodoListOfCategoryScreen主要是一个Lazy Column用来展示任务名称的,其中参数是Category
@Composable
fun TodoListOfCategoryScreen(
category: String
) {
println("TASK TAG: out of column scope")
var tasks by remember {
mutableStateOf(emptyList<TodoTask>())
}
Column(
modifier = Modifier.fillMaxSize()
) {
println("TASK TAG: TodoListScreen: ${tasks.size}, category: $category")
LaunchedEffect(key1 = category) {
tasks = getTasks()
println("TASK TAG: get tasks from api, category: $category")
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(tasks, key = {
it.id
}) {
Text(modifier = Modifier.fillMaxWidth(), text = it.name)
}
}
}
}
首先我们的category是固定的,在getTasks之后,修改了tasks导致Column发生重组,所以scope相关的日志打印了2次。而模拟网络请求的LaunchedEffect
内只调用了一次,是因为LaunchedEffect
的调用时期只有进入重组项或者Key变化重组,如果是普通的启动协程,在重组时,会导致多次调用api请求。
PS: 这里为什么out of column scope也会打印2次呢,其实是因为Column是inline的, 它只能共享调用方的重组范围。
总结
LaunchedEffect
是在Jetpack Compose中提供了在可组合项中调用挂起函数的能力的一种方式。- 在进入组合项时,
LaunchedEffect
会启动一个协程并执行挂起代码块(block),在退出组合项时,协程将取消 - 使用不同的key(参数key1, key2)重组时,会取消当前协程并启动新的协程执行挂起函数
- 写
LaunchedEffect
函数时,最少要一个key(IDE 会有提示) LaunchedEffect
的函数调度器是主线程