副作用问题 - Jetpack Compose 中的 LaunchedEffect 和 SideEffect

Rit*_*esh 5 android android-jetpack android-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
  )
}
Run Code Online (Sandbox Code Playgroud)

我怀疑查询 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}
        }
      }
Run Code Online (Sandbox Code Playgroud)

如果是

Content -> LaunchedEffect(Unit) { launchActivity() }
Run Code Online (Sandbox Code Playgroud)

LaunchedEffect我相信这应该没问题,因为我们只想在第一次组合的一部分时启动活动,并且如果实时数据状态为内容,它将只是组合的一部分

我在第二种情况下遇到了问题,

Error -> {
   // Query 2
  SideEffect { someDialog.value = true // shows a dialog} 
}
Run Code Online (Sandbox Code Playgroud)

如果 的最后状态live-data是 viewModel 中的错误。每次我在TextField 顶层进行更改时,组合编译器MyComposableScreen都会获取(而不是重新组合),并且由于实时数据的最后状态被设置为错误,所以每次都在运行,这很好,因为它应该为每个成功的组合运行并重新组合。invalidatedSideEffect

但是,如果我将其更改为SideEffect对话框LaunchedEffect(Unit){someDialog.value = true}不会每次都显示MyComposableScreeninvalidated那就是所需的行为。

LaunchedEffect(Unit) gets called only if there live-data emits the new state again because of any UI-action.
Run Code Online (Sandbox Code Playgroud)

但是,我不确定其背后的推理,为什么内部的代码在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}
  }
}
Run Code Online (Sandbox Code Playgroud)

它永远不会被重组。再次调用此可组合项的唯一原因可能是组合编译器使视图无效。

我的查询是 -> 当视图/可组合项失效时

SideEffect {someDialog.value = true} 执行,因为它将再次经历组合而不是重新组合,因为 viewModel.response(这是实时数据)最后的状态是Error

但是,如果将其更改为,LaunchedEffect(Unit) {someDialog.value = true} 则在可组合项失效后不会再次执行。它只对由live-data.

问题是为什么?无效应该再次开始合成,因为它是合成。不重新组合的LaunchedEffect 行为应该与SideEffect 这种情况类似,因为两者都对 做出反应composition

Phi*_*hov 14

在 Compose 中,不存在使视图无效这样的事情。

当您将您的when状态变量保持在与状态变量相同的范围内时,更改状态变量会重新组合 的内容when,但是当您将其移动到单独的可组合项时,只有更新viewModel.response才能重新组合它 - Compose 尝试尽可能减少要重新组合的视图数量尽可能。

LaunchedEffect(Unit)将在两种情况下重新运行:

  1. 如果在之前的重组过程中将其从视图树中删除,然后再次添加。例如,如果换LaunchedEffectif并且条件是 firstfalse和 then true。或者,在您的情况下,如果when选择Error ->after ,这也将从视图树中is Content ->删除。LaunchedEffect
  2. 如果传递给的键之一LaunchedEffect已更改。

看来您的问题是LaunchedEffect当新内容值进来时不会重新启动,要解决此问题,您需要按key中的方式传递此值LaunchedEffect,而不是Unit

LaunchedEffect(state) { launchActivity() }
Run Code Online (Sandbox Code Playgroud)


pti*_*nou 5

他们的行为不同只是有原因的。查看文档

对于LaunchEffect,它只会在第一次被调用,因为您已经指定了Unit它的键。如果您希望它在特定的重组时触发,请使用您想要观察的状态值。每次改变时,LaunchEffect都会被触发。