Jetpack Compose Stateful Composables, Explained

Masoud Fallahpour
The Startup
Published in
5 min readMar 1, 2021
Photo by Kelly Sikkema on Unsplash

Jetpack Compose is a modern UI toolkit that uses the so-called Declarative Programming to create UIs for Android apps.

Compose is fundamentally different from XML layouts. Actually, Compose needs a different mental model. To get an idea about what I mean by a different mental model, I strongly recommend reading Thinking in Compose.

There are some fundamental concepts one needs to know in order to master Compose: recomposition, state, etc. In this post, we are going to talk about State by implementing a simple example through an iterative approach.

The “Counter”

We are going to implement a counter using Compose. Our counter consists of two parts:

  • a label showing the value of the counter
  • a button that when clicked increments the value of the counter.

Iteration 0

In this iteration, we just implement the overall structure of the counter. The following code snippet shows the counter.

As can be seen, a Text composable is used to display a label and a Button composable to display a button. Also, there are some log statements to see when the Counter0 composable runs and when the button is clicked.

If we run the above code and click on the “Increment” button 3 times nothing happens UI-wise (as we expect) and the following lines will be printed in the LogCat:

Counter0 ran
Button clicked
Button clicked
Button clicked

So far, so good.

Iteration 1

In this iteration, we introduce a local variable counter to keep track of the value of the counter. We display the value of counter on the label and increment it when the “Increment” button is clicked.

The following code shows our second attempt to create the counter.

If we run the above code and click on the “Increment” button 3 times nothing in the UI changes and the following lines will be printed in the LogCat:

Counter1 ran
Button clicked. Counter = 1
Button clicked. Counter = 2
Button clicked. Counter = 3

As the logs show Counter1 composable is run once. When the Text composable creates the label, counter is 0 hence the label displays “0”.

Clicking on the “Increment” button increments the value of counter but the label is not updated and it always displays “0”. The reason is that although counter increments it doesn’t cause the Text composable to display the new value.

What we want is that whenever the value of counter increments we want Counter1 to rerun (aka recompose) so that the Text composable uses the new value of counter to display the label.

Iteration 2

In this iteration, we change the type of counter from Int to MutableState<Int>. The following code shows our new counter.

MutableState is an observable type in Compose. It has a single property named value. Any changes to value will schedule recomposition of any composable function that reads value.

Let's see how MutableState works by going through the source code of Counter2.

On line 12 we are reading the value of counter to use it as the text of our counter’s label. Also on line 17, we are incrementing the value of counter. Because counter is of type MutableState then whenever we write to it every composable function which is using the value of counter is recomposed. So clicking on the “Increment” button increments (or changes) the value of counter and this causes the Counter2 composable function to recompose.

If we run the above code and click on the “Increment” button 3 times, we expect our counter to show “1”, “2” and then “3”, but it always displays “0”. Also, the following lines will be printed in the LogCat:

Counter2 ran
Button clicked. Counter = 1
Counter2 ran
Button clicked. Counter = 1
Counter2 ran
Button clicked. Counter = 1
Counter2 ran

The reason why the label still displays “0” is as follows:

  • Clicking on the “Increment” button increments the value of counter and it becomes 1. Because counter is of type MutableState and its value has changed, Counter2 composable function is recomposed.
  • When Counter2 is recomposed, a new instance of counter is created with an initial value of 0 and the Text composable displays the value of counter (which is 0).

So basically, every recomposition of Counter2 instantiates a new counter with an initial value of 0 and the label of our counter always displays “0”.

What we want to achieve is to recompose the Counter2 function every time the “recomposed” button is clicked without resetting the value of counter.

Iteration 3 (Final Iteration)

Now we can see the final version of our counter.

The only difference between this version and Counter2 is that the following line

val counter: MutableState<Int> = mutableStateOf(0)

is replaced with

val counter: MutableState<Int> = remember { mutableStateOf(0) }

By using the remember function, we have given memory to Counter3 meaning that when Counter3 is recomposed, it remembers the last value that was stored in counter.

As before, If we run the above code and click on the “Increment” button 3 times, the counter shows “1”, “2” and then “3”, and the following lines will be printed in the LogCat:

Counter3 ran
Button clicked. Counter = 1
Counter3 ran
Button clicked. Counter = 2
Counter3 ran
Button clicked. Counter = 3
Counter3 ran

You can see from the logs that the value of counter is retained when Counter3 is recomposed and because of this our counter is working as expected, finally!

Side Note

If you are familiar with C then using remember to retain a value in a composable is like defining a static variable inside a function in C using the static keyword.

Take a look at the following code.

The output of the above code is:

6
6

This is because every execution of foo creates a new instance of x with an initial value of 5. Now, If we replace int x = 5 with static int x = 5 and run the code, the output will be

6
7

Herefoo is executed twice. In the first run, x is incremented, and “6” is printed. In the second run, because the value of x is retained, x is incremented again, and “7” is printed.

That’s all about creating a stateful composable in Compose. To learn more about state and recomposition take a look at State and Jetpack Compose.

If you find this post useful then clapping is appreciated (1, 2, or maybe 50 claps!)

--

--

The Startup
The Startup

Published in The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +772K followers.

Masoud Fallahpour
Masoud Fallahpour

Written by Masoud Fallahpour

Software engineering @ Klarna. Software engineer, *nix lover, curious learner, gym guy, casual gamer.

Responses (2)