副作用问题 - Jetpack Compose 中的 LaunchedEffect 和 SideEffect
Issue with Side-effects - LaunchedEffect and SideEffect in Jetpack Compose
为什么每次我的可组合项无效时都会调用 SideEffect,但同样不适用于 LaunchedEffect?
sealed class SomeState {
object Error:SomeState()
data class Content(): SomeState
}
class MyViewModel:ViewModel {
internal val response: MutableLiveData<SomeState> by lazy {
MutableLiveData<SomeState>()
}
}
// This is top-level composable, it wont be recomposed ever
@Composable
fun MyComposableScreen(
viewModel:MyVm,
launchActivity:()->Unit
){
val someDialog = remember { mutableStateOf(false) }
MyComposableContent()
GenericErrorDialog(someDialog = someDialog)
when (val state = viewModel.response.observeAsState().value) {
// Query 1
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> {
// Query 2
// Gets called everytime this composable gets invalidated, for eg in case of TextField change, compiler is invalidating it.
// But if i change it to LaunchedEffect(Unit), invalidation has no effect,LaunchedEffect only gets called when there is new update to the LiveData. why?
SideEffect { someDialog.value = true}
}
}
}
// This is the content, which can be recomposed in case of email is changed
@Composable
fun MyComposableContent(
onEmailChange:(email) -> Unit,
email:String,
){
TextField(
email = email,
onValueChange = onEmailChange
)
}
我怀疑查询 1 和查询 2 都是顶级可组合项的一部分,永远不会重新组合,但可以无效,
when (val state = viewModel.response.observeAsState().value) { // observing to live-data
// Query 1
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> {
// Query 2
SideEffect { someDialog.value = true}
}
}
如果是
Content -> LaunchedEffect(Unit) { launchActivity() }
我相信这应该没问题,因为我们只想在 LaunchedEffect
是第一次合成的一部分时启动一个 activity,如果实时数据状态,它将只是合成的一部分是内容
我在第二种情况下遇到问题,
Error -> {
// Query 2
SideEffect { someDialog.value = true // shows a dialog}
}
如果 live-data
的最后状态是 viewModel 中的错误。每次我在 TextField
中进行更改时,我的顶级 MyComposableScreen
都会被 compose 编译器 invalidated
(未重新组合),并且由于实时数据的最后状态被设置为错误,SideEffect
每次都是 运行ning,这很好,因为它应该 运行 每次成功的组合和重新组合。
但是,如果我将其从 SideEffect
更改为 LaunchedEffect(Unit){someDialog.value = true}
对话框不会在每次 MyComposableScreen
为 invalidated
时出现,这就是所需的行为。
LaunchedEffect(Unit) gets called only if there live-data emits the new state again because of any UI-action.
但是,我不确定其背后的原因,为什么 LaunchedEffect(Unit){someDialog.value = true}
内的代码在可组合项获得 invalidated
后不触发,而 SideEffect
内的代码在可组合项后触发失效了吗?
为了更清楚
我明白其中的区别
SideEffect
-> 每一次成功的合成和重新合成,如果它是其中的一部分
LaunchedEffect
-> 当它进入组合并跨越重新组合时,除非键被更改。
但是在上面的场景中-这段代码特别
@Composable
fun MyTopLevelComposable(viewModel:MyViewModel){
when (val state = viewModel.response.observeAsState().value) { // observing live-data state
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> SideEffect { someDialog.value = true}
}
}
它永远不会被重组。再次调用此可组合项的唯一原因可能是 compose 编译器使视图无效。
我的查询是 -> 当 view/composable 失效时
SideEffect {someDialog.value = true}
执行,因为它将再次经历组合而不是重新组合,因为 viewModel.response(这是实时数据)最后的状态是 Error
但如果将其更改为 LaunchedEffect(Unit) {someDialog.value = true}
,它不会在可组合项失效后再次执行。它只对 live-data.
发出的新状态做出反应
问题是为什么? Invalidate 应该重新开始合成,因为它是一个合成。在这种情况下,不重新组合 LaunchedEffect
的行为应该类似于 SideEffect
,因为两者都对 composition
.
做出反应
他们的行为不同是有原因的。看看 documentation.
对于 LaunchEffect
,它只会在第一次被调用,因为您已经为其键指定了 Unit
。如果您希望它在特定重组时触发,请使用您想要观察的状态值。每次更改时,都会触发 LaunchEffect
。
在 Compose 中,没有使视图无效这样的事情。
当您将 when
保持在与状态变量相同的范围内时,更改状态变量会重新组合 when
的内容,但是当您将其移至单独的可组合项时,只会更新 viewModel.response
可以重组它 - Compose 会尝试尽可能减少要重组的视图数。
LaunchedEffect(Unit)
在两种情况下将是 re-run:
- 如果它在之前的重组过程中从视图树中删除,然后再次添加。例如,如果将
LaunchedEffect
包裹在 if
中,条件是先 false
然后 true
。或者,在您的情况下,如果 when
将在 is Content ->
之后选择 Error ->
,这也会从视图树中删除 LaunchedEffect
。
- 如果传递给
LaunchedEffect
的键之一已更改。
看起来你的问题是 LaunchedEffect
在新的内容值进来时没有重新启动,要解决这个问题,你需要将这个值作为 key
传递给 LaunchedEffect
,而不是 Unit
:
LaunchedEffect(state) { launchActivity() }
为什么每次我的可组合项无效时都会调用 SideEffect,但同样不适用于 LaunchedEffect?
sealed class SomeState {
object Error:SomeState()
data class Content(): SomeState
}
class MyViewModel:ViewModel {
internal val response: MutableLiveData<SomeState> by lazy {
MutableLiveData<SomeState>()
}
}
// This is top-level composable, it wont be recomposed ever
@Composable
fun MyComposableScreen(
viewModel:MyVm,
launchActivity:()->Unit
){
val someDialog = remember { mutableStateOf(false) }
MyComposableContent()
GenericErrorDialog(someDialog = someDialog)
when (val state = viewModel.response.observeAsState().value) {
// Query 1
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> {
// Query 2
// Gets called everytime this composable gets invalidated, for eg in case of TextField change, compiler is invalidating it.
// But if i change it to LaunchedEffect(Unit), invalidation has no effect,LaunchedEffect only gets called when there is new update to the LiveData. why?
SideEffect { someDialog.value = true}
}
}
}
// This is the content, which can be recomposed in case of email is changed
@Composable
fun MyComposableContent(
onEmailChange:(email) -> Unit,
email:String,
){
TextField(
email = email,
onValueChange = onEmailChange
)
}
我怀疑查询 1 和查询 2 都是顶级可组合项的一部分,永远不会重新组合,但可以无效,
when (val state = viewModel.response.observeAsState().value) { // observing to live-data
// Query 1
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> {
// Query 2
SideEffect { someDialog.value = true}
}
}
如果是
Content -> LaunchedEffect(Unit) { launchActivity() }
我相信这应该没问题,因为我们只想在 LaunchedEffect
是第一次合成的一部分时启动一个 activity,如果实时数据状态,它将只是合成的一部分是内容
我在第二种情况下遇到问题,
Error -> {
// Query 2
SideEffect { someDialog.value = true // shows a dialog}
}
如果 live-data
的最后状态是 viewModel 中的错误。每次我在 TextField
中进行更改时,我的顶级 MyComposableScreen
都会被 compose 编译器 invalidated
(未重新组合),并且由于实时数据的最后状态被设置为错误,SideEffect
每次都是 运行ning,这很好,因为它应该 运行 每次成功的组合和重新组合。
但是,如果我将其从 SideEffect
更改为 LaunchedEffect(Unit){someDialog.value = true}
对话框不会在每次 MyComposableScreen
为 invalidated
时出现,这就是所需的行为。
LaunchedEffect(Unit) gets called only if there live-data emits the new state again because of any UI-action.
但是,我不确定其背后的原因,为什么 LaunchedEffect(Unit){someDialog.value = true}
内的代码在可组合项获得 invalidated
后不触发,而 SideEffect
内的代码在可组合项后触发失效了吗?
为了更清楚
我明白其中的区别
SideEffect
-> 每一次成功的合成和重新合成,如果它是其中的一部分
LaunchedEffect
-> 当它进入组合并跨越重新组合时,除非键被更改。
但是在上面的场景中-这段代码特别
@Composable
fun MyTopLevelComposable(viewModel:MyViewModel){
when (val state = viewModel.response.observeAsState().value) { // observing live-data state
is Content -> LaunchedEffect(Unit) { launchActivity() }
Error -> SideEffect { someDialog.value = true}
}
}
它永远不会被重组。再次调用此可组合项的唯一原因可能是 compose 编译器使视图无效。
我的查询是 -> 当 view/composable 失效时
SideEffect {someDialog.value = true}
执行,因为它将再次经历组合而不是重新组合,因为 viewModel.response(这是实时数据)最后的状态是 Error
但如果将其更改为 LaunchedEffect(Unit) {someDialog.value = true}
,它不会在可组合项失效后再次执行。它只对 live-data.
问题是为什么? Invalidate 应该重新开始合成,因为它是一个合成。在这种情况下,不重新组合 LaunchedEffect
的行为应该类似于 SideEffect
,因为两者都对 composition
.
他们的行为不同是有原因的。看看 documentation.
对于 LaunchEffect
,它只会在第一次被调用,因为您已经为其键指定了 Unit
。如果您希望它在特定重组时触发,请使用您想要观察的状态值。每次更改时,都会触发 LaunchEffect
。
在 Compose 中,没有使视图无效这样的事情。
当您将 when
保持在与状态变量相同的范围内时,更改状态变量会重新组合 when
的内容,但是当您将其移至单独的可组合项时,只会更新 viewModel.response
可以重组它 - Compose 会尝试尽可能减少要重组的视图数。
LaunchedEffect(Unit)
在两种情况下将是 re-run:
- 如果它在之前的重组过程中从视图树中删除,然后再次添加。例如,如果将
LaunchedEffect
包裹在if
中,条件是先false
然后true
。或者,在您的情况下,如果when
将在is Content ->
之后选择Error ->
,这也会从视图树中删除LaunchedEffect
。 - 如果传递给
LaunchedEffect
的键之一已更改。
看起来你的问题是 LaunchedEffect
在新的内容值进来时没有重新启动,要解决这个问题,你需要将这个值作为 key
传递给 LaunchedEffect
,而不是 Unit
:
LaunchedEffect(state) { launchActivity() }