https://kotlinlang.org logo
#compose-android
Title
# compose-android
m

Masoud Karimi

11/09/2023, 6:54 PM
Hey guys, could someone explain this to me please? Consider this composable function:
Copy code
Column {
    var counter by mutableIntStateOf(0)
    Button(onClick = { counter++ }) {
       Text(text = "Counter $counter")
    }
                    
    // Text(text = "Counter $counter")
}
If I comment the Text the counter will be increased whenever I click on the button. But when I uncomment it the counter is always 0. I think that's because the Text composable is in the main scope of the parent composable function whenever it changes the whole function is recomposed and the counter is reset to 0. Am I thinking correctly or missing something here?
s

Stylianos Gakis

11/09/2023, 7:05 PM
Yeah if nobody is reading the state, there's no invalidation, so no recomposition. With that said, remember to remember your mutableStates
Oh, but the Button, is that not inline?
🚫 2
a

ascii

11/09/2023, 7:06 PM
IDE should highlight it as an error in any case
s

Stylianos Gakis

11/09/2023, 7:06 PM
If it's inline, it would invalidate the parent scope too anyway. In any case though, don't rely on such things, just
remember
it as you should.
a

ascii

11/09/2023, 7:09 PM
Uh what? I swear this used to be an error earlier (red squiggly line). Did something change or am I remembering (heh) wrong?
s

Stylianos Gakis

11/09/2023, 8:56 PM
Maybe the lint does not detect unremembered
mutable{{Int/Float/..}}StateOf
while it does
mutableStateOf
calls? In any case, it’s not a compilation error to do this wrong, you just don’t get the right behavior
s

shikasd

11/09/2023, 9:15 PM
Yeah, seems like an oversight, feel free to file a bug if that's the case 🙂
m

Masoud Karimi

11/10/2023, 11:39 AM
@Stylianos Gakis The IDE gives a warning for unremembered mutable state for me.
I couldn't understand the reason of this yet. If someone can explain it to me I would be appreciate it.
s

Stylianos Gakis

11/10/2023, 11:40 AM
The reason why it needs to be remembered? If that is what you are confused about, take a look at these https://dev.to/zachklipp/remember-mutablestateof-a-cheat-sheet-10ma https://developer.android.com/jetpack/compose/state#state-in-composables
m

Masoud Karimi

11/10/2023, 11:42 AM
I know that by remembering it the problem will fixed. But thing that confuses me is when I uncomment the outer
Text
Composable, the counter won't increase any more
s

Stylianos Gakis

11/10/2023, 11:43 AM
Well, for that question, read this https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78 to understand when a composable recomposes, and what does it mean for there to be multiple “recomposition scopes” in your composable, which may mean that a smaller part of your composable may only recompose if the state read is happening only inside that inner smaller recomposition scope
🆗 1
👍 1
In your case, you got 2 recomposition scopes, the big one ecompassing the entire Column, and the smaller one, which is the lambda of the Button
Copy code
Column {                                                    < -
    var counter by mutableIntStateOf(0)                       |
    Button(onClick = { counter++ }) {     < -                 |
       Text(text = "Counter $counter")      | inner scope #2  | outer scope, #1
    }                                     < -                 |
                                                              |
    // Text(text = "Counter $counter")                        |
}                                                           < -
You got the mutableState defined in scope #1. That does not mean much by itself, for compose to determine when something will recompose, it needs to be reading the mutable state. In the commented out situation: The only place where you read the state is inside scope #2. That means that when the mutableState changes, scope #2 is listening on those changes, since you are using it there, and when it changes it recomposes the Button lambda. In the situation where you do not have the other text commented out: Then you are reading
counter
both in scope #2 and in scope #1. That means when mutableState changes, both scope #1 and scope #2 see the change, and they decide to recompose themselves. When that happens however now, since we are inside scope #1 and it is being recomposed, the line
var counter by mutableIntStateOf(0)
is run again, so the mutableState is set to
0
again. And that’s it, this is what you are observing here. Interestingly! If
Button
was an
inline
function, then the scope #2 would simply not exist, it’d all be one recomposition scope. Which would mean you would see the second behavior regardless on if you have the other
Text
which is reading the
counter
mutableState or not
m

Masoud Karimi

11/10/2023, 12:01 PM
Thank you a lot @Stylianos Gakis. I got it. So as I understood I wrapped the outer
Text
in another
Button
because Button is not inlined, and the counter increases whenever I click on each button. And that makes sense. Thank u again for great explanation. 👍
s

Stylianos Gakis

11/10/2023, 12:07 PM
Yes, that would work, but this is not the real solution here. As mentioned before, the real solution is to remember your
mutableState
defined inside a @Composable function. That’s the verdict you should get out of this discussion.
m

Masoud Karimi

11/10/2023, 12:14 PM
Of course. I knew that. Actually I was testing the recomposition scopes and wanted to understand them better.
👌 1
3 Views