Reputation: 3202
I'm working on implementing MVI using compose. In order for me to follow the proper event loop, I need to propagate clicks events through my view model and then observe side effects. I have looked at a few implementations and they all use LaunchedEffect(true)
to observe side effects and take actions.
I have a similar setup for example:
@Composable
fun HelloComposeScreen(
viewModel: MyViewModel = hiltViewModel(),
onClickedNext: () -> Unit
) {
LaunchedEffect(true) {
viewModel.sideEffect.collectLatest { sideEffect ->
when (sideEffect) {
DashboardSideEffect.CreateParty -> onClickedNext()
}
}
}
Button(
onClick = { viewModel.onEvent(UserEvent.ClickedButton)},
) {
Text("Click Me")
}
}
This results in me using LaunchedEffect(true)
for any screen that has navigation or one time events but the official documentation has this warning
Warning: LaunchedEffect(true) is as suspicious as a while(true). Even though there are valid use cases for it, always pause and make sure that's what you need.
My questions are:
LaunchedEffect
get canceled? The documentation says that it matches the lifecycle of the call site. Is that the composition in this case?LaunchedEffect(true)
setup for observing side effects through my project? What would be an alternative?Upvotes: 7
Views: 7196
Reputation: 2883
The LacunchedEffect
is a Composable
function and it runs coroutines in a coroutineScope
.
The coroutineScope
will be canceled and restarted in two cases:
keys
to LaunchedEffect
gets changed. Changing a passed key from value x
to y
cancels the current coroutineScope
, and then launching the block of code inside LaunchedEffect
again with a new passed keys
.LaucnhedEffect
exits the composition. That means in a later composition if the LaucnhedEffect
does not recompose. For example, because it's inside an if
statement that gets evaluated as false
or if one of the parent composables in the composition tree exits the composition.Example:
@Composable
fun MyComposable(authorId: Int, showReadMore: Boolean) {
// ... logic....
// When showReadMore is false, the latest LaunchedEffect composable exits the composition (the coroutineScope will be cancelled)
if (showReadMore) {
// Changing the value of authorId when showReadMore is true, cancels the coroutineScope and launch the block again.
LaunchedEffect(authorId) {
// Get more info of the author using suspend function
// Since we use LaunchedEffect to run suspend function(s) inside
}
}
}
For the second question: Passing any value like false
, true
, 1
, 2
, and Unit
gives the same result. Passing Unit
makes the code more sense and easier to read because it indicates void
which means that we don't care about restarting the coroutineScope
in the first case (when keys
changes) because the keys are void
.
Upvotes: 2
Reputation: 87804
The LaunchedEffect
is canceled along with its coroutine in two variants:
key
argument(s) is changed - in this case the current LaunchedEffect
will be cancelled and a new one will be created.LaunchedEffect
is removed from the life tree, for example, in case you put it (or its parent at any level) in an if
block and the condition becomes false
.If you do not need to pass any key
that should restart LaunchedEffect
, you can pass Unit
. Any other constant, like true
in your case, is considered suspect because it cannot be changed at runtime and yet may look like complex logic to any coder.
Upvotes: 11