Home > Mobile >  Why can't a variable wrapped with remember be assigned to new value in JetPack Compose?
Why can't a variable wrapped with remember be assigned to new value in JetPack Compose?

Time:08-29

I run Code A and get Result A.

You will find the var temp is always 1 when the system invoke Log.e("my", "Load $temp ${refresh.value}") even if I have clicked the Button again and again.

You know the var temp is wrapped with remember , and I have assigned 2 to it in onClick event of Button.

What's wrong with my code?

BTW, if you run Code B and it will get Result B just like I expected!

Code A

var temp = remember { 1 }
val refresh = remember { mutableStateOf(100) }

Log.e("my", "Load $temp ${refresh.value}")

Button(
    onClick = {
        temp  
        refresh.value  

        Log.e("my", "Save $temp ${refresh.value}")
    }
) {
    Text("OK $temp ${refresh.value}")
}

Result A

2022-08-29 11:05:36.825 29337-29337/info.dodata.soundmeter E/my: Load 1 100
2022-08-29 11:05:37.550 29337-29337/info.dodata.soundmeter E/my: Load 1 100
2022-08-29 11:05:39.596 29337-29337/info.dodata.soundmeter E/my: Save 2 101
2022-08-29 11:05:39.600 29337-29337/info.dodata.soundmeter E/my: Load 1 101
2022-08-29 11:05:43.274 29337-29337/info.dodata.soundmeter E/my: Save 2 102
2022-08-29 11:05:43.278 29337-29337/info.dodata.soundmeter E/my: Load 1 102
2022-08-29 11:05:52.068 29337-29337/info.dodata.soundmeter E/my: Save 2 103
2022-08-29 11:05:52.071 29337-29337/info.dodata.soundmeter E/my: Load 1 103
2022-08-29 11:05:58.509 29337-29337/info.dodata.soundmeter E/my: Save 2 104
2022-08-29 11:05:58.511 29337-29337/info.dodata.soundmeter E/my: Load 1 104
   

Code B

    var temp = remember { 1 }
    val refresh = remember { mutableStateOf(100) }

    //Log.e("my", "Load $temp ${refresh.value}") //I remove it

    Button(
        onClick = {
            temp  
            refresh.value  

            Log.e("my", "Save $temp ${refresh.value}")
        }
    ) {
        Text("OK $temp ${refresh.value}")
    }

Result B

2022-08-29 11:13:30.624 31545-31545/info.dodata.soundmeter E/my: Save 2 101
2022-08-29 11:13:31.750 31545-31545/info.dodata.soundmeter E/my: Save 3 102
2022-08-29 11:13:33.003 31545-31545/info.dodata.soundmeter E/my: Save 4 103
2022-08-29 11:13:38.993 31545-31545/info.dodata.soundmeter E/my: Save 5 104
2022-08-29 11:13:40.158 31545-31545/info.dodata.soundmeter E/my: Save 6 105

CodePudding user response:

AFAIK, by the definition remember { } is just like it's name remembering the value produced by your lambda across recomposition. When you just give it primitive value like Int, 'String' or etc. and the remember is recomposing in any way it will give you your primitive value inside the lambda.

On the other hand, the mutableState or other state API's under the hood will write your last value to the parcelable and retrieve it when the recomposition occurs. So it's like a wrapper object to save last value into memory and retrieve it later using the same object remembered across recomposition by the remebber. And I guess the Log.e("my", "Load $temp ${refresh.value}") code is triggering the recomposition so thats why on your Code A the temp got resetted to its return value from lambda everytime it got recomposed.

CodePudding user response:

Added one more log to explain how this code works.

@Composable
fun CodeA() {
    var temp = remember {
        1
    }
    val refresh = remember {
        mutableStateOf(100)
    }
    Log.e("Test", "temp reset") // New added log
    Log.e("Test", "Load $temp ${refresh.value}")
    Button(
        onClick = {
            temp  
            refresh.value  

            Log.e("Test", "Save $temp ${refresh.value}")
        }
    ) {
        Text("OK $temp ${refresh.value}")
    }
}

If we see, the newly added log does not have any state. It will work every time the code is executed.

Jetpack compose does optimal recomposition. So it will only recompose the code required to update the state change.

Note that only state changes will trigger recomposition.

When you have no logs outside the Button reading refresh.value, the recomposition will happen only for the Button code. But once you add a new Log statement outside the Button, the whole Composable will be recomposed to reflect the state change.

All states in an app should be defined as states. temp should be a state as per your expectation.


Output with refresh.value log

temp reset
Load 1 100
Save 2 101
temp reset
Load 1 101
Save 2 102
temp reset
Load 1 102
Save 2 103
temp reset
Load 1 103

Output with out refresh.value log

temp reset
Save 2 101
Save 3 102
Save 4 103

CodePudding user response:

In Code A

1- The reason temp never survives recomposition is it's a primitive wrapped with remember. Remember stores same object through recomposition.

What does Jetpack Compose remember actually do, how does it work under the hood?

2- The reason your whole Composable gets recomposed is because of scoped recomposition. You read refresh.value inside Button scope and in your Composable scope with log at the top

For every non-inline composable function that returns Unit, the Compose compiler generates code that wraps the function’s body in a recompose scope. When a recompose scope is invalidated, the compose runtime will ensure the (entire) function body gets recomposed (reexecuted) before the next frame. Functions are a natural delimiter for re-executable chunks of code, because they already have well-defined entry and exit points.

Jetpack Compose Smart Recomposition

Why does mutableStateOf without remember work sometimes?

https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78

  • Related