State in Jetpack Compose

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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]
Info
Channel: Android Developers
Views: 62,494
Rating: undefined out of 5
Keywords: Android developers, Jetpack Compose, Unidirectional Data Flow, composables, ViewModels, using State in Jetpack Compose, app development, understanding app analytics, Google I/O, Google IO, I/O, IO, Google I/O 2022, IO 2022
Id: PMMY23F0CFg
Channel Id: undefined
Length: 43min 45sec (2625 seconds)
Published: Wed May 11 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.