[MUSIC PLAYING] ALEJANDRA STAMATO: Hi, there. I'm Alejandra,
Android developer relations engineer. MANUEL VIVO: Hi, everyone.
I'm Manuel Vivo, same team, same title. Welcome to this workshop
where we're going to go through State in Jetpack Compose Codelab. This Codelab is a new one. We have revamped the previous version to offer a more comprehensive
learning experience. It covers all the concepts you need to learn about how to work
with State in Compose. Why, you might be wondering. Well, state
is really important for any app, as that is what gets displayed
on the screen and brings value to your users. I'm going to be sharing my screen with the Codelab text, Android Studio,
and the emulator, so you can easily follow along
as we solve it. We do advise that you try
and solve it yourself as we code along or at a later stage. Yeah, you can find the Codelab by either searching for state
in Jetpack Compose or following the short link
shown on the screen below. So this is the Codelab
we are going to work through today. We will cover how to think
about state and events in Compose, the mechanism Compose uses
to track state changes and update the UI accordingly
with APIs as mutableStateOf, and finally,
how you can use your view models in a Compose app. So we're going to be building
a wellness app. This app will have two sections. In the top section,
we're going to have a water counter, so you can keep track
of the amount of glasses of water that you take throughout the day. And below, we're going to create
a list of wellness tasks, like take a 15 minute walk
or read your favorite book. You'll be able to check
and uncheck each task to mark them as completed or delete them
if you don't want to do them, so you can clear them from the list. Now, we're ready to begin writing our app,
so let's start from the top. What do we mean when we say state? State in an app is any value
that can change over time. This very broad definition includes everything from a room database
to a variable in a class. State determines what is shown in the UI
at any particular time, x so you the most recent messages received in our chat app
or the scroll position in a list of items. In the wellness app, state is the number
of glasses of water the person drinks, the list of tasks, and whether or not
they've been completed. Let's see how Compose uses state
to display information on the screen. Awesome, so let's go
to Android Studio now. And I've created a project in Compose
from the Android Studio templates, and you can find tips to do this
in the step two of the Codelab. So let's jump into step three then. I've also implemented
the first basic composable functions we need to set up in this step, so we can see what we have so far. So first up, we have our main activity, what we are calling the Wellness screen. So if we go to the Wellness screen,
we have a Composable function representing the only screen of our app. Right now, it has the water counter,
but as we progress with the Codelab, we will have more functionality here. And then we jump
straight into the water counter, which is a composable function
with a variable count initialized to 0,
the most basic example of state. And we pass it
into the text composable function so we can display
the amount of glasses of water, and that's our full path. So now, when we open our emulator,
we have our app running, like so. What we see is the output UI, the result of Compose taking your state,
transforming it into the UI that you see displayed on the screen after running
all your composable functions. But as we said before, state is any value
that can change over time, and our state here is static. It's hardcoded 0. So what we want
is to be able to change our state and modify the count
and add more glasses of water. So how can we do this? With events. In Android apps,
state is updated in response to events. Events are inputs generated from outside
or inside our application. For example, the user interacting
with a button on the screen or the app receiving the response
of a network request. In all Android apps,
there is a UI Update Loop that goes like,
state is displayed on the screen. If an event happens,
the Event Handler changes the state, and now the UI displays
the new state on the screen, and so on. This cycle repeats indefinitely. So let's introduce a way for the user to generate an event
to be able to update the state, which means, in our case, change count. So if you follow the tips of the Colab
to implement the changes for this section, you'll end up
with a code snippet that looks like this. Let's take a look at what we added. So we added a button
that receives an on-click lambda function to execute when the button is clicked. We want to increase counting 1, like so. We also added a label to the button, so it says "Add one"
instead of just having an empty button. And to align these two elements
vertically on the screen, we just surround them both with a column, and then we just add
some padding here and there so things look nicer on the screen. We can just run the app
to see how this looks like. In the meantime, to learn more
about column row modifiers and all things layout related, you can follow the Basic Layout
in Compose Codelab right after this one. So we all go to the emulator,
and we run the app. We have a button. We click, and we see that nothing happens. Why is that? Let's see. A bit of theory is coming,
so please, people watching this, bear with me and pay attention
that this is important. We know that Compose transforms data
into UI by calling composable functions. The output of this
is what we call the composition, that is the description
of the UI built by Compose when it executes composables. Now, if a state change happens Compose re-executes
the affected composable functions with the new state,
creating an updated UI, and this is called recomposition. But in order for Compose to recompose
and update the UI accordingly, Compose needs to know what state to track. Compose has a special state
tracking system in place that schedules re-compositions for any composables
that read a particular state. This allows Compose to be
granular and just recompose those composable functions
that need to change, not the whole UI. The small caveat here is that you
need to use Compose's state APIs. As you saw,
using a regular body once in a composable function
don't close recompositions. So which ones are those APIs, Ali? So the API will want to use, so Compose knows
which state it needs to track, in our case, Count,
is MutableState. We can create MutableState
with factory method mutableStateOf, like this. Let's just add the relevant imports,
MutableState class and mutableStateOf factory method, and let's jump
into the definition of MutableState to take a look at what's going on. What this class does
is wrap your state of type t inside a value parameter that
is observed by Compose, this parameter value. So now, what you can do is read your state by accessing this value. So if we go to our water counter now, we're going to see
that we have a compilation error, and we can do what we just said. Instead of accessing count directly,
we can just now call a value. In the same fashion,
instead of printing count like so, we can print a count.value. We format the class a little bit,
and we run the app now. And because we're now using
Compose API MutableState, Compose is tracking changes to count, meaning whenever we press
the button and increase count, the Composable function recomposes, and the UI should show the updated value. Well, not yet because nothing happens. Why is that? The mechanism is working. It is recomposing, believe me, but the problem is that,
when the function re-executes, the variable is reinitialized to 0 again. That's why we always see
the 0 glasses on the screen. We need a construct that saves the state
whenever Compose recomposes this function. This is where Remember API comes in handy, and we can just wrap your mutableStateOf with the Remember
inline composable function like this. Add t, import like so, run
the app, and in the meantime, we can just take a closer
look up to what's happening. A value calculated by Remember
is stored in the composition, and the stored value
is kept across recompositions. So you go to the app and
click our button now, we see that the state is actually working. Nice. Cool. Yeah. So usually, remember mutableStateOf are used together in composable functions because your logical path
would be to update your state and have it survived a recomposition so you can display it properly
on the screen. Talking about syntax now, there are a few ways
to deal with the state APIs. Accessing the value property every time,
like we are doing now, is one way. Another way,
which can be more convenient in this case, is with the By keyword, which uses
Kotlin's delegated properties. With By, instead of a val,
count should be now a var. This approach makes you avoid accessing
value property of the state so that you can modify the [indistinct]
state directly in the code as if it was the actual value. You can see how the code
looks much simpler now. For other syntax options, check out
the Compose state documentation. Each one has its pros and cons. So what have we done so far? We have our state count variable
defined as MutableState, so Compose is able to recompose
whenever this value changes. And thanks to the Remember function,
after the function recomposes, your state survives and is displayed
properly on the UI. Nice. I think we can move on
to the next section then, State Driven UI. Compose is a declarative UI framework. Instead of removing UI components or changing their visibility
on state changes, we described how the UI
is under specific conditions of state. This way,
as a result of our recomposition, composable might end up entering
or leaving the composition. If our composable function is called during the initial composition
or recompositions, we say it is present in the composition. A composable function that is not called
is absent from the composition. So when we
fire WaterCounter composable function to show some UI based on our state. Let's say we wanted to show the text
only if count is greater than 0. So we can just add
a simple IF method like so and move the text inside like this. And now, we have it. And next, what we can do
is configure our button to be enabled as long as count
hasn't reached 10 glasses. We can do this by using
the Enabled parameter in the Button composable function. So we have onClick,
we have the Enable condition, and last, we have the modifier. So you can just format the class
a little bit, and that is it. We can run the app, now. We're going to use
the Layout Inspector here to take a closer look at what's happening as our state changes
and our function recomposes. You can find the Layout Inspector
in two ways, like I just said,
in the bottom right half of the IDE, if you haven't done any further
customizations of the Android Studio. Or you can go to Tools, Layout Inspector,
and then you can open it like that. Just make sure to be running your app on a device using API 29 or higher
for this test. Let's go to the emulator now, and we have the Layout Inspector showing the first initial components tree, where we see,
if we examine it a little bit, in the bottom, we see our button and a text that happens to be
the "Add one" text as we have it in the screen like so. So now, what we can do
is just tap the button. We see that the Layout Inspector
now shows another tree as it refreshed. What the Layout Inspector is showing now is the same button
with the text "Add one," but something has been added. Now, we have the text showing that you
had x amount of glasses of water, so we can see that the tree
has dynamically changed. And now, let's click the Add one button, and we'll see that our composable function
recomposes like so. And the button has been disabled
just like we configured before with the enabled parameter. So this is what we mean
when we say that UI is declarative in Compose. We define what our UI looks like
under certain conditions of state with simple Kotlin logic,
like an IF condition. And if a component
doesn't need to be visible, you just don't add them
to the composition, and they are not added to the tree
that Compose generates. And when the state changes, the state will drive what elements
are present in the final UI. So let's just close the Layout Inspector
and stop it, as we are not going
to be using it further, and we're ready to move on
to the next step. Cool. These actually are very nice
and definitely the power of Compose, being able to define
your UI declaratively. But not only composables
are stored in the composition. Also, the memory they create using
the remember API, for example. That memory or object stored
will be forgotten if your composable function recomposes, and the source location where that "remember"
was previously called is not invoked again. Please make sure to check out
step seven of the Codelab, where we show a detailed example
of that mechanism in action. Cool. Back to our app,
I was wondering what would happen if you have some glasses of water
on the screen, and you rotate the device. Can you try? Sure. Let's see, we can add
just a couple of glasses, and then we can rotate the device. And what we see is-- mm. Okay. Yeah, as I suspect,
the state hasn't been restored properly. Let's see why that happens. Great. So let's go to the water counter, and what we have to do now
is replace remember with rememberSaveable,
and let's give it another try. So why rememberSaveable?
Why this API? While remember helps you
retain state across recomposition, it's not retained
across configuration changes. For this, you must use
rememberSaveable instead. That will persist your state in a bundle. So now what we do is the same test. We add some couple of glasses of water,
and we rotate the device. And we see that our state is persistent. Nice. Nice one. Yeah. Cool. So quick recap-- use rememberSaveable
to restore your UI state across re competitions like remember, but also across configuration changes
like orientation, like this case, or switching to dark mode, and across activity
and process recreation. Definitely. That's a very handy API to know about
in Android for sure. For the next section, we'll need
another theory break, I'm afraid. Are you're ready for it? A composable that contains internal state is a stateful composable. These composables tend to be less reusable
and harder to test. Composables that don't hold any state
are called stateless composables, which are more reusable
and easier to test. An easy way to create
a stateless composable is by using state hoisting. State hoisting in Compose is a pattern
of moving state to a composable's caller to make a composable stateless. State hoisted, in this way,
has some important benefits. First, it has a single source of truth. It can be shared
with multiple composables. It is also intercepted by callers that can decide to ignore
or modify the state, and it decouples the state
from the composable itself. Ali, what if we provide
stateful and stateless versions of that water counter? Yeah. What we want to do
is split the water counter into the stateful
and the stateless water counter, so let's just go open
the water counter composable function and think, what's the state that we
actually want to hoist or extract? And in our case, of course,
it's count, as we saw before. So the first thing
that we want to do, actually, is just move the state
a little bit up like so, so we can see this clearer
outside of the column. So now, we can actually draw
a line like this that will separate visually
where we are defining our state and where we are reading and writing it. So the Refactor
could be quite simple in our case. The first thing that we want to do is just select
the part that will be stateless and just go and hit Refactor function. And let's say that we call this
the Stateless Counter, like so. So let's clean our implementation
a little bit here, and first, what we want to do probably is add a default modifier
for a modifier param. Have our count be
the first parameter like so. And let's clean up count a little bit,
as it created a duplicate version of it, so let's just use count
everywhere like so. And now, obviously, we cannot mutate
count directly here anymore, so what we want to do
probably is expose an event, which is what happens
when the button is clicked. So let's add a lambda function to this. Let's call it onIncrement, like so, and let's pass it down
to the button itself, like this. And that is it.
That is our stateless counter. So now let's go and create
our stateful counter, which will look like--
this is almost done, actually. Let's just refactor and click
Rename as a stateful counter. We then need to-- this is a pop up
that asks us to rename the file. We don't need to rename the file. It's fine.
We can skip this bit. And we almost had it. The state count is defined
just how it was before, and now what we have to do
is just call this stateless counter here, passing the count variable like so. The onIncrement
will be our Lambda function, the behavior when the lambda is called, which is just increase the count by one. And the modifier
will probably be our modifier parameter, like this. And that's the stateful counter also done. Yeah, I mean, if we look
at stateless counter for a second, we can see how it takes
stateCount as a parameter, and it exposes an event onIncrement,
as a lambda function. The pattern where state
goes down and events go up is called Unidirectional Data Flow, UDF, and state hosting is how we can implement
this pattern in Compose. You can learn more about this
in the Compose architecture documentation. Cool. Great. Awesome. So now, if we run the app
after this refactor, you'll notice that everything is working, and we get all the benefits
that we mentioned before. So for example,
your stateless counter here can now be reused
in multiple parts of our app with different count variables-- with different states. So if we define a new state variable,
say for example, juiceCount, we can use
the same layer stateless counter. Instead of using our stateCount, we're going to call juiceCount, like so,
and this is it. This is stateless counter layer
being able to be reused fully. So this is very powerful. Let's just-- that's example number 1. And on the other hand,
the stateful counter, this counter, can provide the same state
that we hoisted count to multiple composable functions
that may need to do something with it. Let's imagine we have
another counter composable function, another counter like so. It receives the same state,
and instead of adding 1 to it, it just multiplies it in two. So it's able to modify it as well, which is also very useful. So make sure you check out the Codelab if you want to take a closer look
at these two examples. So in the meantime,
I'll just clear this a little bit, and that's it. Now that we've finished
with our WaterCounter refactor, it's probably a good time to jump
onto the next piece of functionality that we want for our app,
which is the list of wellness tasks. Sure thing. So we are in the Work With Lists step
of our codelab Array. Let's continue with our wellness app. Let's add the feature that displays
a list of wellness tasks on the screen. Shall we? As we can see on the design, given a task, you can mark it as completed
or remove it from the list. Let's see how this looks. To have our list,
we need to follow a series of steps. Let's take a look actually
to the design first. First, the item. We want to implement
a composable function, like this, with a text, a checkbox,
and a Close button, and both buttons align to the end. Remember that all the code
that I'm going to be writing here, you can develop yourself
following the Codelab tapes, and then you can check the solution. So we're going to have
a composable function that looks like that, and I have it already implemented. It's called WellnessTaskItem. So let's move it
to our main package and close this view. So we'll have the checkbox
that we were mentioning before with a Boolean state checked
to indicate if it's checked or unchecked and an unchecked change lambda function, which is what happens
when the checkbox changes its state. We also have an Icon button
with a on-click lambda function. So I want to hoist this check state
and the events for both buttons to make the function stateless, so we get all the benefits
of state hoisting that we saw before. So let's create
a stateful version of this composable that owns the checked behavior, so your snippet
will look something like this. We define the check state
with the APIs that we learned before. Remember our mutableStateOf,
a Boolean in this case. We passed a state down
to the stateless WellnessTaskItem here. So it's important to notice
that this item is every item in the-- every row in the list, and each check state
is independent per row. So we passed the onCheckedChange
behavior lambda function, so the event can flow up
when the checkbox is pressed. And as per the onClose
lambda function here, don't worry too much about it.
We will implement it in a later step. So next up, what do we have
to get our list completed? We need a model class. We have a WellnessTask. Let's move it to our main package as well. We have this class,
which is a simple data class with an ID and a label representing a task. And what else do we want? We want the actual list
that we were mentioning, so let's just have it here
and implement it like so. Let's take a closer look. So we have a method that creates
fake data to test, in our case, 30 elements
of type WellnessTasks. And what we also have in the list-- is our list composable function finally, receiving a list
of remembered WellnessTask of this method of fake data. And why is it remembered? So the list survives recompositions
when this function recomposes. For instance, if this was a network call, it wouldn't be re-executed again,
for instance. And as we don't know
how many tasks we'll show in our app, we can use the LazyColumn API to display the list of tasks
on the screen. In it, we can use the Items method
to specify what content to show. To learn more about lists,
check out the list documentation or the Compose Layout workshop. Sweet. So now, the only thing
that we're missing in this step is creating the items by calling
the stateful WellnessTaskItem we just wrote before like so. So this is our WellnessTaskItem, and we pass in the task level
of the model that we have. Final step, we call our list
into our main screen, the WellnessScreen. So I want to do, here, is actually add
the WellnessTasksList like so, and so these both things
are properly aligned on the screen, what we can do
is just surround it with a widget, in our case, a column. And we can just use
the modifier from the parent, so it's inherited
throughout the rest of the composable, and that's it. So now, we can run the app, and what we should have
is our water counter like we had before and our list of wellness tasks
with the checked behavior working. And there we have it. Cool, that's very nice, but I think
we might have an issue still. What happens to the state
of the individual items on the list if you scroll down and then back up? For example, let's say you are going
to mark the first tasks as completed, and then you scroll around a little bit. Are they still checked? Let's give that a try. So let's say we checked 0, 1, and 2. I'll scroll all the way
to the bottom of the list, all the way to the top,
and yes, the state-- apparently, we are losing the state. Let's take a closer look
at which state we're losing, actually. So the state we are losing is this one, check state in each item. So what we can do is just use our infamous rememberSaveable API here,
like so, and we run the app again
to see if this fixed the issue. RememberSaveable is useful not only to make your state
survive configuration changes, which is not what's happening here, but also to retain state when your items
leave the composition entirely, which is the case
of our items when you are scrolling, and they are no longer visible. So if we run the app again,
we open the emulator, and we do the same test as we did before. We check 0, 1, and 2. We scroll all the way to the bottom
and all the way to the top, and now, we see that our state
is properly preserved now, only with a seemingly small change and all the power of rememberSaveable. Cool, this looks amazing. Good job.
So yeah, I think that's pretty much it. The only thing left is implementing
the onClose button behavior for our items on the list. Removing tasks from the list could mean mutating the list
that was static before. Apart from mutating the list, we need to make it observable by Compose so that Compose recomposes
whenever the items on the list change. So that's what this section is about. We're going to see how we
can make a list observable by Compose. A list that is observable by Compose is of type snapshot state list, but you don't need
to deal with that API directly. You can use an extension function
on collections called toMutableStateList, or you can create a brand new list using the mutableStateListOf API. Great, so let's start
by modifying the WellnessScreen, and after implementing the tips
in this step of the Codelab, you're going to end up
with a snippet that looks like this. So what do we have here? What we want is our WellnessScreen to be the source of truth
for our task list so that the tasks can be used
in other parts of the screen, not just our list. So first, what we want to do
is move our list of fake data, our fake data method generator, and we define our list like so
to instantiate the list, calling the getWellnessTasks. And to create a list
that is observable by Compose, we call the toMutableStateList
extension function mentioned before. Now, our list is observable by Compose, and any time the list is mutated
by adding or removing items-- we're just going to be removing items
in our case. This will cause a recomposition of the UI. You could have also written
this syntax in another way by using mutableStateListOf, which is similar to mutableStateOf API
that we used before, and it will look something like this. We can comment this bit of code
and show this snippet here. It doesn't matter to add the import
right now. Take a quick look. So we could have done this. One thing to keep in mind, though,
you should create the mutableStateListOf and add all initial elements to the list in the same calculation
of the remember function. So in one single step,
you have your list created with all of the initial values. What you cannot do, though,
is have something like this, in which you first define the list
for the remember function, and then add all the elements
in a different step. Why? Because this will cause that every time this composable
function recomposes, you'll be incorrectly reacting,
duplicating items to the list, and we don't want to do that. So let's just clean this up a little bit. So now is a good time to actually jump into WellnessTasksList and address
this onCloseTask lambda function. Mm-hmm, since wellness app is the single source of truth
for the list, it means that only this function
can modify it. As a best practice,
you shouldn't pass MutableState around as you are going to lose
the single source of truth benefits. Therefore, you need to pass
the functionality of removing items from the list in a lambda
down through the UI tree until it gets to the Close icon itself. Yep, definitely. So now, we can go modify
the WellnessTasksList. So first thing, we can remove
the method that creates fake data, goodbye,
and remove the list default as well, like this remember list here. As we hoisted all of that
to the screen level, let's also rearrange this parameter here to be the first parameter like so. And then we can add
the onClose lambda function parameter to this function and pass it down
to the wellness item. The onClose task receives
a task we want to delete, like so. This is now a brand new lambda function. And now, what we want to do
is just call the WellnessTaskItem with an onClose lambda function that will receive our onCloseTask, and we pass in the task that we have. And that is it. Because it is a good practice
to pass composables only what they need, we don't want to pass
the entire task model down to the WellnessTaskItem because we have the task
hoisted at this level, and it's not necessary. One thing to add at this point is that the Items function in LazyColumn
can take a key parameter. By default, Compose tracks items on a list
based on their position on the list. If the position of that item changes, Compose needs to recompose
all the items shown on the screen. That feels suboptimal when an item just changes its position
on the list itself, but we can make
Compose track items on the list in a different way by specifying a key. In this case, we want
to uniquely identify tasks based on their ID, not on their position. Sure, let's do that. This is how you write it,
by adding the key to the items itself. Yeah, so in this way, if an item
on the list just changes position, Compose can identify that instance
in the composition and move it to the right place
without having to recompose all the items. Awesome. Great. So that's it for the list composable then. We can jump into the WellnessTaskItem and pass in our onClose lambda function
where it needs to be. So we have our task name. We have modifier as a last parameter, and now, we can define
an onClose lambda function. Here is what we were aiming
to implement later, so we can just pass it down. And now, the stateless WellnessTaskItem already receives onClose
into the Icon button, so everything is connected now. We can run the app,
and what should happen now is that both our check state
and our delete functionality are working now. So let's just open the emulator,
and we see exactly this. We see the check behavior is working, and the removing 1, 2 and 3
is working as well. Cool. Oh, wow, we've done a lot of things in just a short amount of time. We have a list
that is observable by Compose. Each item on the list
also holds mutable state for the checked and unchecked status, and that state is also observed by Compose and preserved when items leave
the composition or the app goes
through a configuration change. Yeah, that's great, but now that you mention
configuration changes, I wonder what if we remove
some elements of the list and then rotate the device. Yeah, good point.
Let's see what happens. So now, I already removed
a couple of items within the 0 and 4, and if I rotate the device now, what we see
is that they are actually back, and there is probably another test
that we could do. If we check elements 1, 2, and 3,
and say we remove 1 and 2, and now we rotate the device... there. So they are all back,
so the state is not restored properly. The check state was restored
as if the item was not deleted at all. What we would have wanted to see
is the items 1 and 2 fully removed. So rememberSaveable
is not going to help us this time to retain all changes to the list,
including deletions. We will need a custom saver in this case, and you can read more
about this in the documentation and the Codelab around saving
your state for our app. However, another alternative
is to use ViewModels as a way to deal with state preservation
across configuration changes for the list. So, Manuel, can you tell us
a little bit more about ViewModels? Yeah, that's one of my favorite topics, and that's exactly what the next state
of the Codelab is about. If you're familiar
with ViewModels in Android, you would be very interested in learning how to work with ViewModels
in our Compose app. But in a nutshell ViewModels
provide the UI state an access to the business logic
that might be located in other layers of the app. Additionally, and the reason
why we are mentioning ViewModels here, is because they live longer
than the composition, which means
they survive configuration changes. This makes them a good fit
to host your state, for example, our list of tasks. There is a lot to learn
about ViewModels in Compose, so I would recommend reading
the State in Compose documentation if you want to know more
about their role in a Compose app. So let's migrate the list and the remove behavior
over to the ViewModel. Let's start by creating
our ViewModel class, and I have it here
that we're also going to put in our main package like so. And we can implement it again
following the Codelab tips, so let's take a closer look
at what we are doing here. We have a list defined
in the same way as before, in a private variable task this time because we don't want
to expose the mutable list from the ViewModel to the exterior,
so anyone can modify it. Instead, what we want to do
is just expose a task variable that is read-only. And we also moved here
the fake data generator method. So now, what we have to do
is just instantiate this ViewModel in our WellnessScreen. We can access a ViewModel
from any composable by calling the ViewModel function. To do this, this function is in a library
that we need to add to our app. And I'm just going to show, real quick,
which one this library is. The library is
the lifecycle-viewmodel-compose here. I've already added it
and synchronized my project, so we can use it directly now. So let's jump back into the WellnessScreen
where we can use it. What we're going to do
is just define my ViewModel using this function
as a parameter, actually, with a default value, so it can be hoisted
if anyone needs to later. We add the relevant import
and format the class a little bit, and that is it. Actually, this is a very common pattern
in Compose actually. If your parameter can potentially benefit
from state hosting, go for it. The caller can always ignore it
if they don't need to intercept it. In this case,
the ViewModel could be replaced and faked for testing, for example. Also, this ViewModel function
returns an existing ViewModel or creates a new one
in the given lifecycle scope. It is scoped
to the closest ViewModel store owner, which is the activity, in this case. So I remove our fake data method because we have it
in the ViewModel now, like so. And what we also want to do
is remove the definition of the list because now it's stored
by the ViewModel itself. So now, let's just delegate
all of our state and the behavior
that we have in the ViewModel down to the WellnessTaskItem like so. The task will look like this, and we are calling
the ViewModels remove method. And we're done. So now, we run the app, and since the state
is kept outside of the composition and stored by the view model, mutations to the list survive
configuration changes. We can do the same test that we did before
by checking 1, 2, and 3, removing 1 and 2 tasks,
rotating the device, and now what we see is
that the state is properly preserved thanks to the ViewModel. Cool. It is working.
Nice one. Then I think that's pretty much it. Are we missing any state that we might want to migrate
to the ViewModel as well? Yes, definitely. We could also migrate
the check state over to the ViewModel, so our code is simple, more testable, and all our state managed
by the ViewModel. We actually covered
this final step in our Codelab, so make sure you go there
and check it out. Definitely. Oh, one last thing about these ViewModels
is that it is a good practice to use them in screen level composables. You should not be passing ViewModels
down to other composable functions. That's going to violate
the single source of truth principle, and also you could get scoping issues. You can safely use
ViewModels in composables that are closed to an activity, fragment, or the destination of a navigation graph. But as I mentioned before,
check out our web docs if you want to know more about the role
of ViewModels in Compose. Yes, definitely great points there. So yeah, that's what we have for you today. Congratulations. We worked through the State
in Jetpack Compose Codelab. Yay, well done. We covered how to think
about state and events in Compose, how Compose tracks the changes, and how you can use your ViewModels
in our Compose app. But there is so much more that you can do. We recommend
that you take a look at Jetnews our sample Compose app that showcases the best practices
that we explained today. Also, check out
the final section of this Codelab for links, documentations,
further reads, and APIs. Thanks for watching this workshop and please let us know in the comments
if you are still with us on whether or not you coded along with us. Thanks, and have a nice one.
Bye. [MUSIC PLAYING]