
Jetpack Compose, the new modern UI toolkit for Android (and other platforms), is now the spotlighted topic in the Android dev world and there is no doubt it is going to change the way we build UI on Android sooner or later.
With a single touch to change the way we define local variables in functions, we can now make it a “state” and embrace the auto-managed UI update (recomposition) bring to us by the compose internal.
But while we enjoying the MAGIC brought to us by Compose, have we ever wonder why do we even have to write in this exact syntax? Shall the code work as usual if we drop any of the component?
Let’s take a look at a simple Composable
function (or say, a screen).
Here we have a button that displays the current value of a
and it increments a
when it is clicked. Magically, when we run it and click the button, you’ll see the value of a is preserved and incremented every time
Now let’s see what would happen if we declare a
the same way we used to do for local variables.
If we run this and press the button, you would see nothing change with the UI. So why? Since a
is now just a local variable, changing it doesn’t cause composable to recompose, and as we know that composable functions emit changes of UI, these change to the in-memory a
is never emitted to the UI.
This is where mutableStateOf
comes into play.
This function returns a SnapShotState
that preserve the value by the internal Snapshot System and would trigger composable recomposition if a change occur to the managed state. We can rewrite our sample screen as this.
By making a
a state, the place where we read
the value of a
is now essentially subscribing to the change of a
(just like observing a LiveData
), in this case, the Button
‘s content composable (the curly bracket that wraps the text, aka the lambda we pass to button) would be recompose every time the value of a
changes.
Hmmm 🤔, now you might be thinking that everything seems to work perfectly at this stage, so…. why do we even bother to remember
?? Doesn’t declaring it as state already remembering it? (Finally get to the topic of this post)
Well, let’s try to break our sample screen again.
See the difference? Now we have another Text
to display the value outside of the button. If we run this and press the button, you would see that the both of the text never changes. Why does this happen??
If you take a closer look at my statement few paragraphs ago
By making
a
a state, the place where weread
the value ofa
is now essentially subscribing to the change ofa
(just like observing aLiveData
), in this case, theButton
‘s content composable (the curly bracket that wraps the text, aka the lambda we pass to button) would be recompose every time the value ofa
changes.
and apply to the case of composableScreen4
, since we have another Text
composable that is reading a
the wrapping composable, which is the SampleScreen
*, is now subscribing to a
, so every time the we press the button (= change value), the SampleScreen
would get recomposed (= re-executed)
And in this case, every time we execute the SampleScreen
, we are creating a new state
object with initial value of 0, and resubscribing the child composables to the newly created state
! This is why even though the recomposition occurs on user interaction with the use of state, we can never see the updated value being rendered.
Note:
Column
is aninline @Composable
so it is not the one observing the state
Well guess how to solve this issue, remember
!
The remember function would cache block we pass to it into a slot table, with a Don't invalid
flag and the calculation
block. The underlying operation would check the table to see whether it should
- Cached the value if it is yet presented in the table
- Redo the calculation and update the table if
invalid
flag is set to true
and finally return the resulting value of the calculation block.
When SampleScreen
get executed and come across the line where we declare a
, we now check the slot table to see whether the value is previously cached or not.
- At the first time, it is yet cached so the state creation would be performed and cached inside the table
- When recomposition, it sees the cached (and updated) state inside the table so just return the latest value
With both remember
and mutableStateOf
, our sample screen could function normally just as the very first example!!
After all of this attempt, we kinda have a clearer idea of what compose is doing behind the screen (get it? behind the SCREEN😉), but…. do we really have to know this in order to write in Compose UI? The answer is indeed “NOT REALLY”, as compose is built simple-and-easy-to-use by a fascinating team. However, knowing how it works under the hood may come into help when trying to debug in certain situation.
Now forget about these for now and happy COMPOSE your UI 👊
I am an Android Dev working at LINE Fukuoka and we also run a Podcast, EN/JP available. You could also find me on twitter @kuramu1108