top of page

Ultimate Guide to States, Viewmodels And Lifecycles in Jetpack Compose | Only Guide you will need.

If you are coming from the React world, you might already be aware about states well. Similar to React jetpack Compose also has states for the composable. So if you are aware about React, then this blog will be an easy go for you. Even if you are not aware about React, then don't worry. Stay until the end of this blog where we will discover exactly that.

In the project, you can see that I have a basic increment counter and a decrement counter. So I have a text view and two buttons to increment

and decrement the counter.

As the name suggests, the increment counter

should increment the counter value and update the value in the text view, and the decrement counter button should decrement it

According to the logic. It should work fine. Right? Let's try it out

If we click on the buttons, We can see that it is not working as expected. I clicked on the increment and decrement counters, but it is not working as expected.

The counter variable, however, is actually incrementing and decrementing both. So if I go ahead and click on the increment counter button, you can see that the value is incrementing. And similarly, when I go ahead and click on the decrement counter, you see that the value is now incrementing. But still we are not able to see anything onto the screen. Why do you think that is?

The reason to this is that the text view was composed once. Now it is not taking the live value of the counter because it is not aware that the value of the counter variable is changing, so it is not recomposing itself.

So to make it aware of the value change we need a way basically, so that the text view knows that when is the value of the counter variable going to change.

To do that, we have states. Let's change the counter variable from a normal

of value to a state.

 Here I have changed the counter variable to be a state, and it is a mutable state. That means a state whose value we can change also notice that I have changed the usage

of Counter variable to Counter Dot value. Since now counter variable is a state and

state value returns to the actual value that we want.

So you can think of state as this way. Let's consider I'm having a switch now. A switch can be in one of the two states.

A light switch can either be closed or open.

So, When it is open, the light turns on and when it is closed, the light turns off. Similarly, there can be objects that can have

more than two states as well. Take ourselves for example, we can be in a happy state, a sad state, a depressed, or maybe an emotional state.

So in the project as well, I have turned the counter variable into a state, so it can be considered as a box that is containing an integer. At the start, we are having the value zero inside that box.

So now that we have done that, let's try out our app once more.

Once again, we notice that the text view is not taking up that value. Why is that?

Well Compose app transforms data into UI by calling the composable functions. We refer to the Compostion as the description of the UI built by compose when it executes composable. If a state change happens, compose re-executes, the affected composable functions with the new state, creating an updated UI that is called the Recomposition. Compose, also looks at what data an individual component needs. so that it only recomposes components whose data has changed and skips those that are not affected.

So in our case, since the counter variable was being modified and used by the text view, as the counter variable changed, the text view got recomposed, but when it recomposed itself, then it re-initializes the counter variable back to zero. Now, if that is the case, why does nothing happen in the counter variable that is logged?  I mean, if you notice, if you increment the counter, we'll see the

value one. Whereas if we decrement the counter, we'll see

the value minus one.

when we are incrementing the counter, the value is going up to one, and when we are decrementing, the value is going to minus one. The value of the variable is increasing and decreasing. So why doesn't it reflect in the text view? Well, the answer to that lies in the previous statement that I mentioned.

Compose looks at what data an individual composable needs so that it only recomposes components whose data has changed and skips those that are not affected.

So in our case, the button does not need that counter variable. It only increments or decrements. The value of counter variable is needed by the text view, so that's why it recomposes the text view and not the buttons. Logging a value is not considered since it is not being used by composed to show onto the screen. Well, now that we know that, why is this happening, Let's take a look at how do we solve this?

 Now, as you notice, I have added or remember keyword that wraps the state. This, remember, keyword preserves the value across recompositions. That means any value calculated by, remember, keyword is stored onto the memory. This way, when a recomposition of component occurs, it does not initializes a state.

Now if you see that when I go ahead and increment the counter, the value increments, and similarly, when I increment the value decrements as usual, this is what we were hoping for. Now there's one more way to use this Remember keyword, instead of using the assignment operator, we can use the by keyword so that we get the data type of the value directly.

Changing the keyword to by produces some error. Well, this is a bug in jetpack compose. As for now, If you are reading this blog in the future, this bug might already have been resolved for you. If not, you can solve this by changing the

import statement. So go ahead, find the Remember keyboard, and

instead of, remember, you just have to import everything. you see that the error will be gone.

Now, Coming back onto the topic, you'll notice that now counter dot value is not valid. That is because using the by keyword automatically assigns the value as the data type for the variable. So when I go ahead and hover over the counter variable, you see that the data type of this

variable is now integer, which was the data type of the value that I have put into the state. that means counter variable is now off integer data type.

So using by keyword will directly assign the value that it returns. As the data type to the assigning, you can now change the usage of the counter variable.

So instead of counter dot value, you have to go ahead and remove, Value keyword from the counter.  So we now know the usage of remember keyword as well. The remember keyword is well and good. However, we still have that problem in composed like we used to have in XML layouts, configuration changes. As you were already aware of the life cycle methods that happened in XML layouts and how the data wasn't stored on config changes. Well, we have the same problem in here as well. Because ultimately it is the activity that is running the jetpack compose app.

So config change in the activity means that it'll get recreated and so will all the compositions and the variables as well. So here I have incremented the counter up to seven. Now when I rotate my phone, you see that the counter value is initialized back to zero. This is a classic problem of configuration change. So as you can see that I'm not able to save the state across configurations. Now, how do we solve that?

Well, to solve that, we have something called the Remember saveable.  So if we change the counter, we're able to store the value from, Remember to remember saveable, we'll tackle this issue.

So once again, I'll go ahead and increment the counter, and then I'll do the configuration change. But now you can see that now the counter variable is not being initialized back to zero.  So now you can see that no matter how many times I rotate my phone, the counter variable will not be initialized back to zero and it'll maintain its state across configuration changes as well.

 So what is happening in the background is that the remember saveable function is actually saving the value of the count variable in a bundle. It automatically does that for all the values that can be stored in a bundle. If you want some more complex objects to survive, the conflict changes, we can create our own methods to do so. One more method to save complex objects would be to make them into parcelable. If you'd like more control over the data, you can create your own savers as well


Hey guys,

just a quick pause here. Just want to tell you that if you're interested in learning about the advanced concepts of jetpack compose such as creating our own animations, gestures, views, and layouts, then do check out my course here.


 Jumping onto another hard topic in jetpack compose the view models. Obviously, many of you might be wondering that How does a view model fit into jetpack compose. In XML layouts, The view models were responsible for managing the logic and surviving the conflict changes. Same goes here. The view models are responsible for creating and managing the logic of the app and also surviving the recompositions. Here I am assuming that you are already aware of the view models. I will not be going deep into why view models are required. Instead, I will be telling you how are view models used in jetpack compose. Now, before we dive deep into creating the view models in jetpack compose here, I would like you to understand about the life cycle of

a composable.

First, we might already be knowing about the life cycle of an Android app, but how does a composable react to those lifecycle changes is the main concept to catch here.

View models were created to bind to the life cycle and manage the data logic accordingly. But then one question arises, and that is when we talk about jetpack compose, how does it affect the life cycle? And also are the view models now attached

to the life cycle of the composable or the activity?

Well, As we found out that a composable has a composition which describes the ui of our app, and is produced by the running compostables, a composition is a tree structure of the composable that describes the ui. Now, when our app runs the composable for the first time, which is called the initial composition, it keeps a track of the composable that we call to describe the UI in a composition. And then when the state of our app changes, jetpack compose schedules are recomposition for that Composable.

Recomposition is when jetpack compose re executes the composable. That may have changed in response to state changes, and then updates the composition to reflect any changes. A composition can only be produced by an initial composition and updated by recomposition. The only way to modify a composition is through recompositions.

Here is the composition life cycle of an object.

Image credit -

The life cycle of a composable is pretty simple when compared to activity life cycle. So the life cycle of composable in jetpack compose can be divided into four distinct phases

  1. Creation

  2. Composition

  3. Disposal

  4. Recycling


1. Creation

The first phase of composable Lifecycle is creation. this is when the composable is first created and added to compose tree. At this point, the composable does not have a parent and is not yet part of the composition.

2. Composition

The second phase of a composable lifecycle is composition. This is when the composable is added to the composition and becomes part of the compose tree. The compose composable now has a parent and can be used in the composition.

3. Disposal

The third phase of a composable lifecycle is disposal. This is when the composable is removed from the composition and is no longer part of the compose tree. The composible is now orphaned and Can no longer be used in the composition.

4. Recycling

The fourth and final phase of a composable lifecycle is recycling. This is when the composable is reused in a new composition. The composable is given a new parent and can be used in the new composition and this lifecycle.


A composable gets created, enters the composition, gets recomposed zero or more times, and then leaves the composition, which causes the disposal and recycling of that composable.Now in this life cycle, composition and recomposition are deemed to be the most important aspects of the life cycle. Recomposition is typically triggered by a change to a state object. Compose tracks these changes and runs all the composable in the composition that read that particular state, and any composable that they call that cannot be skipped.

If a compose is called multiple times, multiple instances are placed in composition. Each call has its own life cycle in the composition.

For example,

we have called two text views in the column composable. Now, both these text views are different instances of the same text view composable, and the instance of a composable is identified by its call site.

NOTE: Call site is the place from where the function was called.The compose compiler considers each call site as distinct.

Now, one thing to note here is that compose identifies which composable was Called or not during the recomposition And will try to avoid recomposition if their inputs haven't changed.

So let's consider an example.

In this example, let's say I call it from two different places. Now the show feedback composable will have different call site. So I mentioned that compose identifies which

composables were called or not during the recomposition, and will try to avoid recomposing them if their input haven't changed. So in this example, if you notice that for every instance the show error text is conditional, it might show or it might not, but the thank you text will be shown every time. That means since the input of the text is

not changed for any instance, compose will not recompose the Thank you Text when recomposing the show Feedback composable.

Many times what happens is that compose doesn't have any information to uniquely identify a call to a composition.

Here. If you now notice that compose doesn't have any information regarding the text composable. Moreover, since it is being called from same call site, so for each time the show is composable, enters, recomposition, all the text views will also recompose. This might cause some unwanted behaviour sometimes to avoid this from happening. We add some extra information to help in smart re-compositions.

That extra information in our case would be to add a key composable. This key composable helps uniquely identify each instance of the text during a recomposition for jetpack compose to figure out whether to recompose this instance or not. However, in our case, the string that we passed onto the key might or might not be unique. This is a bad practice. Ideally you would want to pass in a unique value for key so that compose can uniquely identify each instance in the call stack.

Now, let's first understand that how does jetpack compose skips a recomposition? How does it know when the inputs have changed or not?

well, if a composable is already in the composition, it can skip recomposition if all inputs are stable and haven't changed.

Now, what does it mean when I say inputs are stable? A composable is said to be stable when

  1. the result of equals for two instances will forever be the same. For the same two instances.

  2. If a public property of the type changes composition will be notified,

  3. All public property types are also stable.

All deeply immutable types can safely be considered stable types. Now that we know about stables. So when all types passed as parameters to a composable are stable, the parameter values are compared for equality. Based on the composable position in the UI, tree recomposition is skipped if all the values are unchanged since the previous call.

That's how composed compiler knows when to skip a recomposition. This is called smart recomposition.

We can also create our own stable item if we want the compose compiler to treat it as stable. We can mark it with the stable annotation.

Here, If we notice that we have marked the interface as stable, This is because we are sure of that this interface is stable. Generally, interfaces are not considered stable since they can change based on the implementation. But here we have marked an interface as stable because it follows all the principles that we have mentioned.

  1. In here the result of equals for two instances will forever be the same, for the same two instances. So for any two same instances of UI state, the result will always be the same. Either it'll have an error or a value.

  2. If a public property changes composition will be notified of the same.

  3. All the public property types are stable as well. Well, a throwable is always stable since at one point of time it can only have one value. All primitive types such as boolean, integer, float, et cetera, are also considered to be stable as well. Same goes for the generic type T because for one instance of UI state, it can only have one value of T.

Now marking our interface with stable annotation, it allows jetpack compose to favor smart re compositions.

 So for the view models, we already know that view models should be responsible for handling all the logic and maintaining life cycle awareness. So this is clear for sure that view models still remain attached to the life cycle of the activity and not the composables. That means all the composable inside of a view will be having one view model instance, which will maintain the logic of the states for all the composable.

Currently in our view, all the logic to increment and decrement, the counter is residing inside the view itself.

Let's change that and introduce view models.

So here you can see that I have already added a view model class and inherited from the view model. So this is nothing new from what we did in XML layouts. We just add a view model as usual, then we create the counter value in a view model. There's only one difference in jetpack compose view models, and that is now the view models can also handle the state values. so we initialize the counter value of the view model by a mutable state. Now the view model will store the state values and would be binded to the life cycle of the main activity in our case. Then we also add the logic to increment and decrement the counter values.

Finally, we initialize a view model inside of the main activity,  So here you can see that I have initialized the view model inside of the main activity as usual and also used view models, increment and decrement functions to increment and decrement the counter value. And in the text view I have used view model dot counter for the value to be printed onto the screen. Let's run the app.

 so you see that the values are incrementing and decrementing correctly, Now, one of the major purposes of view models was to survive the configuration changes. So if I go ahead and rotate my app, you'll see that the value of the counter variable will still persist across the configuration changes as well. So  here we'll see that the view models are working as usual. They are binded to the life cycle of the activity and maintaining the state value of the counter, even on configuration changes, which serves the purpose of the view models.


 So let's keep the blog Until this point, I hope that you learn something

of value in this blog. Thank you for reading the blog and for your time, we'll meet in another blog.

Until then, keep contributing Code to Humanity.

18 views0 comments

Book an appointment

bottom of page