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
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
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.
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
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.
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.
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.
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.
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.