1

What's the difference between LaunchedEffect and Composable function? Except coroutine scope in launch effect

val currentProgress = GlobalLoader.current
LaunchedEffect(progress) {
    progress ?: return@LaunchedEffect
    onProgressCatch()
    currentProgress.showIf(progress)  
}
@Composable
private fun ListenProgress(
    progress: Boolean?,
    onProgressCatch: VoidCallback,
) {
    progress ?: return
    onProgressCatch()
    GlobalLoader.current.showIf(progress)
}

1 Answers1

2

When a Composable enters composition LaunchedEffect() gets triggered. If LaunchedEffect has key or keys it can be triggered on recompositions when key values change.

It provides a coroutineScope that can be used with animations.

And one time actions that supposed to happen when a condition is true

val currentProgress = GlobalLoader.current
LaunchedEffect(progress) {
    progress ?: return@LaunchedEffect
    onProgressCatch()
    currentProgress.showIf(progress)  
}

this one only triggers when your Composable enters recomposition and when progress value changes. And since you check for nullability it only gets invoked when null till progress ?: return@LaunchedEffect when not null rest of the code is invoked.

@Composable
private fun ListenProgress(
    progress: Boolean?,
    onProgressCatch: VoidCallback,
) {
    progress ?: return
    onProgressCatch()
    GlobalLoader.current.showIf(progress)
}

Is invoked every time recomposition happens. If your progress is not null it will be called several times if you run an animation for instance that could trigger recomposition on every frame your callback will also be invoked

You can check out other question about LaunchedEffect for more detail

LaunchedEffect vs rememberCoroutineScope. This explanation makes me confused. Please make it clear to me

How can I launch a function only onetime when I use Jetpack Compose?

Use of LaunchedEffect vs SideEffect in jetpack compose

how can I do text timer

Edit

Let's consider 2 versions of a function as in question.

@Composable
private fun ListenProgress2(
    progress: Boolean?,
    onProgressCatch: () -> Unit,
) {
    progress ?: return
    println(" ListenProgress()2")
    LaunchedEffect(key1 = progress){
        println(" ListenProgress()2")
        onProgressCatch()
    }
}

@Composable
private fun ListenProgress(
    progress: Boolean?,
    onProgressCatch: () -> Unit,
) {
    progress ?: return
    println(" ListenProgress()1")
    onProgressCatch()
}

And they are called in parent as

@Composable
private fun Sample() {
    var value by remember { mutableStateOf(0f) }

    Slider(value = value, onValueChange = { value = it }, valueRange = 0f..100f)
    val progress: Boolean? = if (value > 5) true else null


    ListenProgress(progress = progress) {
        println(" Hello World $value")
    }

    ListenProgress2(progress){
        println(" Hello World2 $value")
    }

}

If slider is moved in logs you will see something similar

 ListenProgress()1
 I   Hello World 5.4463773
 I   ListenProgress()2
 I   ListenProgress()2
 I   Hello World2 5.4463773
 I   ListenProgress()1
 I   Hello World 6.5251245
 I   ListenProgress()1
 I   Hello World 7.3171854
 I   ListenProgress()1
 I   Hello World 8.200772
 I   ListenProgress()1
 I   Hello World 9.236124
 I   ListenProgress()1
 I   Hello World 9.781246
 I   ListenProgress()1
 I   Hello World 10.948928
 I   ListenProgress()1
 I   Hello World 11.660738
 I   ListenProgress()1
 I   Hello World 13.263601
 I   ListenProgress()1
 I   Hello World 14.22418
 I   ListenProgress()1
 I   Hello World 15.987328
 I   ListenProgress()1
 I   Hello World 16.761938
 I   ListenProgress()1
 I   Hello World 18.055727
 I   ListenProgress()1
 I   Hello World 18.598866
 I   ListenProgress()1
 I   Hello World 19.164701
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Regarding the second option recomposition happens only if input data is changed. Am I right? In both cases body runs only if "progress" is changed. So what is the difference for me as for a developer? Which approach should I use? – Vadim Zhukov Dec 03 '22 at 00:56
  • But if progress is not null the one without LaunchedEffect gets invoked on every recomposition you might end up invoking `onProgressCatch()` every frame let's say an animation is running. When it's one time event you should use `LaunchedEffect` – Thracian Dec 03 '22 at 03:28
  • According to the documentation 'Recomposition is the process of calling your composable functions again when inputs change'. Why my ListenProgress recompose when animation is running if 'progress' value didn't change? This is skippable function – Vadim Zhukov Dec 03 '22 at 03:54
  • What you posted is true but it also depends on your callback. If you callback is reading a value from parent Composable it might cause your `ListenProgress` to be recomposed. What i posted is a possibility in your example it might not be the case. – Thracian Dec 03 '22 at 04:38
  • My answer is to show that there is a difference, not to say you should always use `LaunchedEffect`. If recomposition is to be triggered and you want your callback to be called once or keys change go with LaunchedEffect otherwise it makes no difference – Thracian Dec 03 '22 at 04:52