Naveed
Naveed

Reputation: 3202

Why is LaunchedEffect(true) suspicious?

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:

Upvotes: 7

Views: 7196

Answers (2)

Mahmoud
Mahmoud

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:

  1. When the passed 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.
  2. When the 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

Phil Dukhov
Phil Dukhov

Reputation: 87804

The LaunchedEffect is canceled along with its coroutine in two variants:

  1. The passed key argument(s) is changed - in this case the current LaunchedEffect will be cancelled and a new one will be created.
  2. 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

Related Questions