0

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
HelloCW
  • 843
  • 22
  • 125
  • 310

3 Answers3

2

In Code A

1- The reason changes in temp don't survive recomposition is it's a primitive wrapped with remember. Remember stores same object through recomposition. The value 2 you read are the ones before recomposition.

If you store your primitive inside an Object

class MyObject(var value:Int)
var myObj = remember{MyObject(0)}
myObj.value++

would return updated value on every 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

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • So there are two reasons why this is happening: 1. Primitive variable, 2. `refresh.value` call in the outermost scope, right? – Taras Mykhalchuk Aug 29 '22 at 05:18
  • 1
    primitive value is reason for temp is not updating, remember stores same primitive or `Object`. However even if it's same object you can update properties . This is why temp is not updating but if you wrap it inside an Object you return same instance but with updated values. So that's a way to bypass remember returning same object. It's widely used by the way to not instantiate and lose your current values or to not instantiate memory heavy object on every recomposition – Thracian Aug 29 '22 at 05:21
  • 1
    whole Composable recomposing is because of reading value outside Button's scope too. When you read value of a State that section of your Composable is recomposed. If you check the first link you will see that even if i update a value inside another function my whole Composable got recomposed. This is because of scoped recomposition. Any function that is not `inline` and returns `Unit` is considered as a scope. So when you read a value Compose checks out for closest scope that should be recomposed. – Thracian Aug 29 '22 at 05:24
1

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
Abhimanyu
  • 11,351
  • 7
  • 51
  • 121
0

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.

axelbrians
  • 316
  • 4
  • 7