What's the different using SideEffect and not using it in JetpackCompose?

I try to understand SideEffect of Jetpack Compose.

Other than the official document, I find 3 other reference

  • https://jorgecastillo.dev/jetpack-compose-effect-handlers
  • https://www.section.io/engineering-education/side-effects-and-effects-handling-in-jetpack-compose/
  • https://medium.com/@umairkhalid786/jetpack-compose-side-effects-sideeffect-1e9995b6d423

I'm am still confused. My simple question as below

What's the difference if I do this with SideEffect

var i = 0
@Composable
fun MyComposable(){
    Button(onClick = {}){
        Text(text = "Click")
    }
    SideEffect { i++ }
}

and without SideEffect

var i = 0
@Composable
fun MyComposable(){
    Button(onClick = {}){
        Text(text = "Click")
    }
    i++
}

Code example from https://www.section.io/engineering-education/side-effects-and-effects-handling-in-jetpack-compose/

Is there a way the i++ is still triggered in one case but not the other? How can I create a way to experiment with that?


Solution 1:

The SideEffect function is a scope of code triggered outside of the Compose Function. I found a way to differentiate them.

If I run it as below

@Composable
fun TrySideEffect() {
    var timer by remember { mutableStateOf(0) }
    Box(contentAlignment = Alignment.Center) {
        Text("Time $timer")
    }

    Thread.sleep(1000)
    timer++
}

The above code will only show 0. The timer++ has no impact, as it was changed while it is being composed, given it's part of the composable function.

However, if we use SideEffect as shown below, given it is not part of the compose function, the timer++ will trigger this, and this will make the composable function recompose again and again (Given SideEffect is being called on each Composable). This will make the Text show 0, 1, 2, 3, 4...

@Composable
fun TrySideEffect() {
    var timer by remember { mutableStateOf(0) }
    Box(contentAlignment = Alignment.Center) {
        Text("Time $timer")
    }

    SideEffect {
        Thread.sleep(1000)
        timer++
    }
}

Additional Info

To make it a little interesting, if I put on the below code, then the text will display 0, 2, 4, 6 ... (given the first ++timer will happen without composing, and the ++timer that happen in the SideEffect will trigger it)

@Composable
fun TrySideEffect() {
    var timer by remember { mutableStateOf(0) }
    Box(contentAlignment = Alignment.Center) {
        Text("Time $timer")
    }

    SideEffect {
        Thread.sleep(1000)
        timer++
    }

    Thread.sleep(1000)
    timer++
}

Another interesting note, comparing SideEffect with LaunchEffect

If we use LaunchEffect, the number will only increment once, i.e. from 0 to 1. This is because unlike SideEffect, LaunchEffect only triggered on the first recomposition, and not change on the subsequent recomposition (unless we change the key1 value, so it will be triggered upon change of key1 value.).

@Composable
fun TrySideEffect() {
    var timer by remember { mutableStateOf(0) }
    Box(contentAlignment = Alignment.Center) {
        Text("Time $timer")
    }

    LaunchEffect(key1 = Unit) {
        delay(1000) // or Thread.sleep(1000)
        timer++
    }
}