副作用问题 - 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} 对话框不会在每次 MyComposableScreeninvalidated 时出现,这就是所需的行为。

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:

  1. 如果它在之前的重组过程中从视图树中删除,然后再次添加。例如,如果将 LaunchedEffect 包裹在 if 中,条件是先 false 然后 true。或者,在您的情况下,如果 when 将在 is Content -> 之后选择 Error ->,这也会从视图树中删除 LaunchedEffect
  2. 如果传递给 LaunchedEffect 的键之一已更改。

看起来你的问题是 LaunchedEffect 在新的内容值进来时没有重新启动,要解决这个问题,你需要将这个值作为 key 传递给 LaunchedEffect,而不是 Unit:

LaunchedEffect(state) { launchActivity() }