Jetpack Compose basics code-along

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] FLORINA MUNTENESCU: Hello, everyone, and welcome to our Compose Basics Code-along. I am Florina Muntenescu, a Developer Relations Engineer In the Android team, and I'm joined here today by my colleague, Jolanda Verhoef, who also works as a Developer Relations Engineer. JOLANDA VERHOEF: Hi, everyone. In this session, we will be live coding various programming challenges that you might have when you first start developing your app using Jetpack Compose. FLORINA MUNTENESCU: So as Jolanda is coding, feel free to open up Android studio and follow along, or you can just sit back and watch and do the codelab yourself at a later stage. We'll also be looking at the live chat on the YouTube stream. So if you have any questions, ask them in that chat. Our colleagues, [INAUDIBLE] and [INAUDIBLE],, will be answering them there. And then if we see that there are a lot of questions being asked to the codelab, we'll make sure that we also answer them here. JOLANDA VERHOEF: So during the session, I will be sharing my screen to make sure that you can code along. So let's get started. And we will open up a browser first, and we will search for the Jetpack Compose Basics codelab. That's the codelab that we will be following today. So let's look that up and let's open up the codelab. There we go. FLORINA MUNTENESCU: During this session, we'll start by creating a new Compose project. We'll see what the Android Studio template generates. And then we'll start tweaking the UI. We'll learn how to reuse composables, how to add columns and rows, and then we'll dive into state and compose and learn what state hoisting means. We'll finish by creating a performance lazy list, persisting our states, animating our list, and theming our app, depending on how much time we have. JOLANDA VERHOEF: So let's start by going into Android Studio and open up a new project. So I will be using Android Studio Arctic Fox here, which is the latest stable release of Android Studio. And here I can start a new project. If you have earlier also worked with Android Studio, you can also use File, New Project instead. So let's go with New Projects here, and let's see what is being created for us. So we have to choose a template here. And what we will do is we will open the Empty Compose Activity template. And this will set up all the boilerplates that we need in order to build a Compose app. So we press Next, and then we have to choose a name for our application. Let's go with Basics Codelab because that's the thing we're building right now. And you can choose another package name or another location if you want to. I will just leave these as they are. And a minimum SDK, so make sure that you have at least API 21, which is the lowest supported version for Compose. But if you want to go higher, that's of course also a possibility. So I will press Finish here. And while this is being set up, I will go back to the codelab and I will ask Florina a question. Can you tell us a little bit more about what Compose actually is? FLORINA MUNTENESCU: Yes. So Jetpack Compose is a modern toolkit designed by Google to simplify UI development. It uses a reactive programming model. And you write your UI using Kotlin. Compose is a fully declarative UI framework, which means that you describe your UI by calling a series of functions that transform data into UI hierarchy. When the underlying data changes, the framework automatically recalls this function. This function is updating the view here before you. OK. Has the project built? Can we see how this looks in our project? JOLANDA VERHOEF: It's almost there, but we can already see the main activity, so I think we can continue. FLORINA MUNTENESCU: Oh, in the composable. So Composer says these so-called composable functions. So these are actually just regular functions, but they're marked with the @Composable annotation. And then each of these, in turn, can call other composable functions like here the text. And a function is actually all you need to create a new UI component. Because saying composable functions every time is quite long, we're just going to call these composables for short. JOLANDA VERHOEF: So let's take another look at this main activity. So as in the old viewer, the activity is still the entry point for our application. So the activity has an onCreate method which shuts the things up for your application. And inside an onCreate method, you can see the setContent call. And that's actually our way to move to transition into the Compose world. So within that setContent block, we can start calling composable functions. So inside here all the way to the bottom, you see that Greeting is being called which is a composable function that was mentioned before by Florina. And surrounding the Greeting method is Surface. And the Surface is a kind of a container composable. So this is also a composable, and I'll quickly dive into the actual implementation with Command-B or Control-B to see what that looks like. So as you can see, this is also a composable function, and it has a lot of values that we can set in order to adapt the values of that surface. In our case, a lot of these have default values, so we don't have to adapt them. They have reasonable defaults. But we can change things if we want to. So let's go back into our main activity and see what else is here. We have a Basics Codelab theme, and this is a way for us to style, to give it a certain material styling. And we will talk about that a little bit later. And then if we go all the way to the bottom, so what we can do is we can actually run this application on our emulator or on a device, and we will do that later. But we can also use a preview, which is directly inside Android Studio. And I will open it, and this will have to build for a bit. But you can actually see that you can create another method and just call it Preview or whatever name you want. Just make sure it doesn't get any parameters. And inside, you can also call these composable methods and that will actually generate for you a specific preview that you want. So while this is building, let's take a look at what else we will be doing. FLORINA MUNTENESCU: Yeah. So right now, we just have a simple project. But you'll see once the project builds, you'll see that it just displays a gray thing, and I think that's a bit boring. So I think we should start updating the UI and changing the color of the grading. JOLANDA VERHOEF: There it is. FLORINA MUNTENESCU: Yes. [INAUDIBLE] JOLANDA VERHOEF: There we go. FLORINA MUNTENESCU: We just have a text on the screen right now. JOLANDA VERHOEF: So how would we tweak that, Florina? What do we want to do with it? FLORINA MUNTENESCU: Let's change the color of the grading and add some spacing around it. Yeah, exactly. That's what the Codelab suggests us to do. JOLANDA VERHOEF: OK. So the first thing we want to do, let's say that we start by changing the background color. So as you can see, this is now just plain white, and let's try to make that a purple color instead. So what we can do in order to get that done is we can use that same surface that we were using before. So we can actually surround our text, and we can surround it with a container. In our case, the container is the surface container, which is a material component that we can use, and basically, you can set colors for that container, and then it will just put whatever you put in there, it will put on top of that color. So in our case, we can set the color of that container to be-- and I'm using material theme here, which is, again, a little bit about the styling of your app. So we will use the color primary here. And if we use that primary color, and we can use the different colors from the material design, that should actually update the backgrounds of our surface. And one thing that you might notice as well if you look at this is that it's not just changing the background's color, but actually our text color changes as well. It's now white. And that's because material design is an opinionated framework. And that means that it tries to do the best things. So it tries to make sure that even if you do not make a lot of changes to your code, it actually does them for you, and it tries to behave in a logical manner. So in this case, what our surface actually does, if we go into it again, we can see that when we set the color, it actually chooses a content color for you. So it calls a function to see what the content color should be. And in the first case when we had a white background, it was using black. But in the case that we use our primary color from our theme, which is purple, it actually chooses the white color on top. So this way, we can actually create a nice UI. And now we want to add some spacing around this. And this actually introduces a second big concept of building UI with Compose, and that's modifiers. So next to creating composables and nesting composables, you can also add modifiers to basically all your composables. And the modifiers, you can set and they basically modify your composable, which is why they're called modifiers. And there's a lot of them. Like, you can see there's padding, there is a focusable modifier, a clickable, a background sizer, a whole range of different modifiers which all, in some way, modify the composable that they're being set on. So in our case, we want to add some spacing around our text. So we will use padding. And the padding that we will set is 24 dp. So in the meantime, if you get stuck during building this, if I'm going too fast, in the codelab there's actually a lot of places where there is a bit of code of where you should be at that moment. So when you get stuck, trying to go back to the beginning of your-- or to the step in the codelab, and you can just copy and paste this. Let's see. So while we're building this, it is having some issues with importing. There we go. Let's see if it already imported. So it uses all these imports from the Compose libraries in order to get things done. And in our case, I think it's having some issues importing the 24 dp. So while it's building, let's go back to the codelab and see what the next thing is that we want to do. FLORINA MUNTENESCU: Maybe we can copy paste the import form the codelab just in case it can't find it exactly there. JOLANDA VERHOEF: There we go. FLORINA MUNTENESCU: Jolanda mentioned that there are a lot of modifiers available. We actually created a cheat sheet for you to easily find or somehow group the modifiers available. So check out our documentation for our cheat sheet of all of the modifiers available and what you can actually use in Compose. JOLANDA VERHOEF: Great. In the meantime, I got it done. The spacing is showing around the text, and it's nice and purple. So just the way we wanted to design it. FLORINA MUNTENESCU: And we actually got one question on the live stream. Do we actually need to wrap inside-- our composables always inside the basic codelab theme? Or is it enough to just do it in the main activity? You don't have to do that. I think in the next step, we'll start seeing how we can reuse composables, and we'll start creating multiple composables. And you'll see that you don't have to do this for each of them, but rather only the one that's at the top of their hierarchy will have to be wrapped inside the theme. And then that material theme will actually propagate the changes further down. I will go more in the theme. Maybe, we'll have time to go over theming towards the end of the codelab. Cool. So right now, Jolanda is just using one composable, so just the greeting. But then with time, the more components you add to your UI, the more levels of nesting and upgrading. Pretty much, the same with any other function or code base. The problem is that this can affect the readability if a function becomes really large. So by making small reusable components, it's easy to build up a library of UI elements used in your app. And then each one can be responsible for one small part of the screen, and then it can be edited and previewed independently. JOLANDA VERHOEF: So let's try that with our code. And of course, this is just going to be a tiny extraction of a little bit of code, but indeed, when your code gets bigger and bigger, you want to extract code more and more often. So in our case, let's extract most of our app into a composable called MyApp. So I will use a live template in order to create a composable function. You can also just type this out if you want, and let's call it MyApp. And inside, let's extract this whole piece, which is the surface and then the greeting inside of it. So I will move this into MyApp, and instead, I will just call MyApp directly here. So now I did a simple extraction to a separate composable. And this is not going to change anything in our design, but this is mostly to demonstrate how you can actually move those composables outside of each other. FLORINA MUNTENESCU: So we've learned how to use composable functions and modifiers. We reorganized our code a little bit. In the next section, let's increase the complexity of the UI. So our goal is to create a UI like the one that Jolanda is showing right now, so we have two greetings, buttons, and spacings. So what do we need to do? To build this UI, I think we're going to use some more basic composables provided by the library. So we'll use a column, which lays out the children vertically. And then inside the greeting, we will use a row that lays out the children horizontally. JOLANDA VERHOEF: Yes. So let's take a look at the design again. What do we want to do? We want to create these two Hello, World and Hello, Compose items. And somehow, we need to lay them out vertically. And then inside, indeed, we want some sort of horizontal alignment. So before we do that, I'm going to make one more change to our code, and that's what I'm going to adapt our preview because this one is still calling Greeting, but instead we want this to call MyApp. That way, when we make changes to MyApp, we will actually see those in our preview. We could also add another preview if we wanted. Like, we can choose how we want to create previews for our app. In our case, let's just directly call MyApp here. So the first thing that I want to do based on the design is I want to break up our Hello, Android into Hello, World, so like two vertical texts instead. And so the first thing that I'm tempted to do is to basically just copy my text and use it again so that I have two texts, and then change the first one to be Hello comma, and then the second one can be just the name. Right? So here, we will just use the name that's being put into the greeting. Now, let's see what that would look like. I think it must look quite nice, but I do see that we have the padding here two times, so probably there will be-- oh, actually, they're shown on top of each other. That's not what we want. And that's the thing that we mentioned we want to align them vertically. So what surface by default does is it behaves like a box, which means that it lays its children on top of each other. You can align within that box. But what we want to do is we want to actually lay them out vertically, so we want to surround this with a column. There we go. And now, I do expect them to show up on top of each other. So within the column, it basically looks at which composables are here, and then just puts them underneath one another. So this starts to look a lot better, but we do see that the padding is now done for each of the texts individually and I think we might want to move that into the column instead so that the Hello, Android is nicely stuck together. So let's move this into our column and clean up a little bit so we can simplify our texts. There we go. And then this modifier can go as well. And as you can see, by just adapting our coupling code, we can quite easily change what our design looks like. And we can see it directly in Android Studio. I haven't run this app on any device yet. So that's the first thing we wanted to do. I think that looks quite nice. So let's take another look. We want to get to this point. So the next step is let's try to create more than one greeting, and let's try to put in some dynamic data here. So we want to make sure that we can call the greeting with the words world, and we want to call it with the word compose. So for that, we're not going to adapt the greeting itself anymore, but rather, we will make changes to the MyApp composable. So what we can do is we can simply add another greeting here. And instead of using Androids, we can use World and we can use Compose. We are doing the same trick as we just did with the texts. So we can surround this with a column again, and then those two greetings will show on top of each other. Now, of course, this is fine, but what if we are going to have 10 or 20 of those greetings? That wouldn't really scale, right? So what we can actually do is we can put the names into the MyApp composable as a parameter. So let's give the MyApp composable a parameter called Names, which is a list of strings. This way we can add two or three or whatever we want. And let's give that a default value. So let's say that we're going to pause World and we're going to pause Compose as the default values here. And instead of creating this greeting by hand by duplicating it, we will actually be able to surround this with a for loop. So this is just a normal coupling construct that we can use. And instead of having to do something fancy, we can easily just use the coupling for loop, and then for each name in Names, it will call the greeting and put it as a child of the column. So now, I am still calling this with World, so that would lead to World two times. So let's call it with the name, and then I would expect this to give Hello, World and Hello, Compose. Now, this will probably have different sizes as you can see. And that's because, by default, this column will just use whatever space its children need. So in order to demonstrate that a little bit better, let's, for a preview, go to a width of 320 pixels, which is kind of like a small device. And if we now scale this, we can see that, indeed, it nicely snugs that 24 pixels of padding and not any more. So if we want to change this behavior, we want to modify one of our composables. Right? We want to modify our Greeting composable. So what we can do is we can use another modifier for this. You guessed right. So in this case, we can use the fillMaxWidth modifier, which tells Compose to use, basically, the full width that is available in the parent container. So let's see what that would look like. So the column now fills the whole width. And because the column is inside the surface and the surface has the background color sets, everything will become that same color purple. So that starts to look a little bit better already, but I do see that everything is purple now. And I think in our design, we had some spacings around this. So we want to adapt our composer a little bit more. We want to modify them a little bit more. And in our case, we want to add some extra padding surrounding our surface. So we could use another modifier for our surface, and we could use modifier again, the padding. But as you can see, there's different padding methods here. So we can use all, horizontal, start, stop, end, et cetera, we can choose which one. So in our case, what we can do is we can set the horizontal and the vertical value here. So our horizontal could be, for example, 8 dp and then our vertical could be, let's say, 4 dp. So let's align this, and let's run it again and see what that looks like. I'm expecting this to add some space into the left and right of each of the greetings, but also a little bit of spacing to the top and bottom. That starts to look quite nice. Now, the only thing I'm missing is a little bit of extra space on the top and the bottom of the whole app. So what we can do is we can go back to our MyApp, and we can give this column a bit of extra padding as well. So we can, again, create a modifier here, and say let's do some extra vertical padding of 4 more dps. And that way, I think we have our spacing down. Now, the only thing that's left is to add the button. So we've only been working with columns so far, so let's try and create a row. Let's see. Where we put the button? It needs to be inside our greeting and inside the surface because it's inside the purple rectangle that we have. And then it should be somewhere after the column. So let's edit over here, and I'll just create a button which has an onClick listener set to it. This is a callback, and we will actually fill this in later on. So far, we're not dealing with the behavior of the button yet. And inside the button, we can put whatever other composables we want. So in this case, we're just going to add a simple text composable which will say Show More. There we go. But we can also put, I don't know, a whole other UI hierarchy, again, inside that button, so that works quite nicely. And while we do this, I haven't laid out this button in the parent yet. So this button will just be shown right on top of our Hello, World text. So we probably want to do something special here. Look, it shows just on top of the rest. And you can also see that it currently has a primary color, and that's because a default button, by default, uses the primary color. So we can use the OutlinedButton instead, which is another concept from material design, which will use the surface color as a background. And that will give us the nice white button that we saw in our design. Now, we want to put this in a row as we discussed. So let's put both our column with the text in it and the outlines buttons together inside a row. So I will do another surround with widgets, and I will surround this with a row. And this will probably not work yet because we made our column fill the max width. So what this is going to do is the column will fill that while width, and there will be no space left for our button. So instead, what we can do is we can actually go and, let's see, remove that fillMaxWidth. There we go. But then again, it won't use the full width, so we can use another modifier instead which is the weight modifier. And the weight modifier, you might still remember it from the view system, it tells the parent, the row in this case, that this child, our column, it wants to be flexible. And it wants to take up any space that it can, but it doesn't want to overlap with other children. So this is making this column a flexible child while our outline button is not a flexible child. So our outline button would just take space that it actually needs. And as you can see, this starts looking much better. The only thing is that our button is still aligned quite weird to the right top. And that's because it is our column that still has that 124 pixel padding. And instead, we want to move that to their whole row. So let's move it out here. And inside our row, let's add a modifier.padding with 24 dp. And as you can see, this .dp, actually, for us, handles the whole density independent pixels, which is quite nice. And this is starting to look like it. I think, let's take a quick look at our codelab to see if this is the same. FLORINA MUNTENESCU: I think it is. JOLANDA VERHOEF: I think we did. FLORINA MUNTENESCU: Yeah, yeah. Cool, awesome. So we got a question around XML. It's composed totally XML free or in some corner cases, we still have to use XML. Oh, UIs are not defining XML anymore. You can just use Kotlin to define your UIs. But axial still use XML for things like strings, so for these kind of resources. JOLANDA VERHOEF: Yeah, and it's also still possible, of course, to interrupt. If you still have a lot of XML views, and you slowly want to migrate to Compose, you can actually still call your XML from within Compose and the other way around. There's actually a really great other code around this, around migrating your app to Compose. FLORINA MUNTENESCU: And there's actually a Code-Along on migrating to Compose tomorrow. And I think, also, things like the Android manifest will still be using XML, of course, but I think that's a default in Android. OK. So we're starting to add some more life to our application. Let's do some more. So there is a lot that you can do with composables and modifiers, but for now, we actually haven't added any behavior to the app. So let's see how we can expand the greeting when the user clicks the button. So the text inside the button should also change, right? We should see either Show More or Show Less depending on the state. So before getting into how to make a button clickable and how to resize an item, you need to store some value somewhere that indicates whether each item is expanded or not. So this is the state of the [INAUDIBLE].. Since we need to have one of these values per greeting, the logical place for this is inside the Greeting composable. So as we mentioned before, Compose apps transform data into UI by calling composable functions. And then if your data changes, Compose re-executes these functions with the new data, creating an updated UI. So this process that I've just mentioned now is called a recomposition. Compose also looks at what data is needed by an individual composable so that it only needs to recompose components where those data has changed. And then skip recomposing the ones that are not affected. This also means that composable functions can execute frequently and in any order. So please, don't rely on the ordering in which the code is executed or how many times the function will be recomposed. OK. Jolanda, let's see how we implement the state in our Greeting composable. JOLANDA VERHOEF: Yeah, that's a lot of theory, Florina, so let's see how we could actually implement this. So this is step seven of the codelab. If you got stuck, the bottom of step six has a great codes that you can just copy over, and then you can continue with us from step seven. And so what our goal is for now is we have this beautiful button that says Show More. But we actually want to be able to press it, and then it expands our row, it expands our greeting, and then it says Show Less. And then if you click it again, it will change the button again. And that's how we can actually use state. So let's go into our code and see what would be my first intuition-- would be, well, I want to make a variable which we call, let's call it expanded, and which starts being false because we're not in the expanded state initially. And when I click the button, I want to change that value. So the expanded states would be whatever it wasn't before, right? So I'm negating the value. I'm setting it, and then for demonstration purposes, indeed, show more or show less based on that expanded state. So if expanded, I would say Show Less, and if not, I would say Show More. Now, this code actually will not work. So I will quickly run it on my emulator to see if it works. But I can tell you already that it won't. And the reason for that is what Florina just mentioned is that just setting a value here isn't actually going to do anything. So what we need to do is we need some way to tell Compose that when this value changes, it needs to recompose any composables that depend on that value. And in our case, the composable that depends on this value is our text because the text changes when the value of that variable changes. So in our case, when I just have this button and I press the button, yeah, maybe my expanded variable will actually change to true, but that doesn't mean that this text composable will be recomposed and, thus, show different text now. So we need some way to kind of signal to Compose that this is a value that it needs to keep an eye out for. And the way we do that is we use the mutableStateOff. So this is a Compose method that we can call with an initial value. And that mutableStateOff signifies that this is a value that actually needs to recompose its dependence when it changes. So this now returns us a state instance instead of just a Boolean. That also means that we cannot just call it like this, we need to actually set and get the value of it. And although this all looks quite nice, as you can see, it already shows us a big red line here. And that's because we cannot just use mutableStateOff. And the warning actually says that we need to use the remember keyword here. And remember is used in Compose for another purpose. So let's take a look at what remember does. So let's import it, and let's see what it says. So remember, make sure that when this greeting is being recomposed, so not the text that depends on our expanded value, but when this greeting up here is being recomposed because, for example, our name changes or because of whatever other reason, as Florina mentioned. Like, this can recompose as often as it wants. And remember, make sure that the value that's created inside it will not be reset every time that this composable is being recomposed. So the first time this greeting is being called, which is the initial composition, it creates this mutableStateOff. But then, when the greeting is being recomposed, it doesn't actually do that again. So it doesn't reinitializes it to false. Instead it just remembers what it had before. So remember is a great name I would say for this. And we could actually use the var keyword here, and that gives us a actual state inside this composable that will actually work. So let's check if this actually does work. I'm running it on my emulator as we speak. So it will reset. And while it's doing so, let's see if clicking the button now actually changes the text that's shown on the button. There we go. So as you can see when I press Show More, it actually changes to Show Less. So that's amazing. Now, the only thing that we should actually also do here is to change the padding, so let's create a variable called extraPadding, which we will make-- well, that depends on the expanded value. Right? So if this is expanded, then this is going to be, let's say, 40 dp and else 0. And again, we have to use the dot value here. There we go. So this extraPadding, let's set it to, I don't know, the column can use that extra padding, and then set it only to the bottom of the column. So there we go. We can set it to that extraPadding. Let's realign this a little bit. There we go. And let's run and see if this actually works. So one thing that you might notice is that I'm not using the remember value here. So that means that every time our greeting is being recomposed, if you remember correctly, then this variable will be recalculated as well, even though, maybe, our expanded state didn't really change. Now in this scenario, we're just doing simple if else statements, so this is not a very complex calculation. But if this calculation would be way more complex and would take more CPU cycles, then we could actually opt to also put this inside a remember block. Let's see what this looks like. And indeed, we can see that now the padding nicely expands and collapses. So we have states. FLORINA MUNTENESCU: Cool Yeah. So in the case of the expandable grading, our state lives there inside that grading composable. And you can write and read the value of the expanded state only inside that one composable. So there is no need to know about the state anywhere else because this is really specific. It's a great thing. But in general, state that is read or modified by multiple functions should live in a common ancestor, so in a function that calls the greeting. And this process of moving the state somewhat higher is called state hoisting. And then hoist means to lift or to elevate. Making state hoistable avoids duplicating state and introducing bugs, and also helps us reuse composables. And it just makes composables substantially easier to test. Contrarily, a state that doesn't need to be controlled by a composable's parent should not be hoisted, which is the case for the greeting. So keep in mind that the source of truth belongs to whoever creates and controls that state. So in the next section, what are we going to do? We want to build an onboarding screen. So whether we want to show the onboarding screen or the greeting screen will depend on some state as well. So how do we do this? JOLANDA VERHOEF: Well, let's first start by copying over the code for this onboarding screen. We've already dived quite deep into designing screens, so I will quickly just copy over the implementation here. And you can do the same if you're following along. So let's copy over what we have, and we will quickly walk through this. I will put it below our greeting and above our preview. So let's put it over here. Let's see. I didn't copy it correctly. There we go. So what are we doing here? Well, you can see at the bottom that we're creating another preview. This time, we're setting the width and the height. So while we're going, let's hide the emulator for a bit, and let's take a look at that preview. There are still some errors here because we have to add some imports. And let's see. So we want to import getValue and mutableStateOff. And then we also want to import setValue. There we go. We want the imports fillNextSize. That's close to the fillNextWidth we used before. There's an arrangement and alignment, all of these setting certain values for our design. And I think that's about it for our imports. So let's take a quick look at this. The first thing that I'm noticing is that, here, I see again that same concept of remember mutableStateOff, in this case, setting the default to true. But here I see something else that we didn't see before. I see the by keywords. And maybe, Florina, do you want to explain what the by keyword does? FLORINA MUNTENESCU: Sure, sure. So it's using delegates. Right? So in this case, when we would be using shouldShowOnboarding, we no longer use the dot value like we are in the greeting. Actually, can we change the remember and the way we used to remember in the greeting? JOLANDA VERHOEF: Yeah. Let's. FLORINA MUNTENESCU: Here, let's see what happens. I think we'll need to use a var as well. And then now, we can just remove the dot value. JOLANDA VERHOEF: So let's take all those dot values that we had, there we go. FLORINA MUNTENESCU: Yeah. Using property delegates in Kotlin allows us to simplify the code a little bit. JOLANDA VERHOEF: Thank you. That actually looks a lot better. So I was already getting annoyed with writing that dot value everywhere, so I'm happy that we got this fixed. OK. So that was the first thing I saw here. There's a big to-do here, which we will get to later. And let's take a quick look at our design. You can see that we have a surface again, so that was the container from material design. Then we have a column, in this case, which is filling as much space as it can. So in our preview, we can see that is 320 by 320 pixels. And it is using a vertical arrangement and a horizontal alignment. So vertically, what this column is doing is it's trying to center all of its children in the center of the column. And then horizontally, every child within the column will also be centered. So in the end, that leads to this result, which is our text and our button nicely in the middle of our container. And as you can see, there is a little bit of spacing between our button and our text and, again, we're doing that using the padding modifier. And one more thing that you see here on the button is that we have this onClick parameter again. And that onClick parameter itself gets a method gets a function actually. And so this is a callback which, actually, tells the buttons that when it gets clicked, it should call this function and should execute what's happening inside. So in this case, we're setting this shouldShowOnboarding value to false. So that's what this currently looks like. But we just have the onboarding preview. But if I run my app, we're not actually going to see this onboarding because this OnboardingScreen is not used anywhere. So in order to use it, I think we would have to make changes to MyApp. And the first thing I'm going to do is I'm just going to, actually, change the name of MyApp to Greetings because this was our column which had a list of greetings inside. And then, we will create a new MyApp composable, so again, I'm using the live templates here and creating a new MyApp function. And inside, I will have some sort of logic to either show the OnboardingScreen or show the Greetings screen. So it will be something like if shouldShowOnboarding, if we want to show the onboarding, then we will call the OnboardingScreen, and else, we will call the Greetings screen. Now, this is the state that Florina was mentioning. Right? Do we want to show the onboarding, yes or no? So that var we had inside our OnboardingScreen actually doesn't really belong there. So this to do, let's hoist the state, and let's move it to the point where it actually needs to be read. In our case, that is the MyApp composable. So I'm just moving our var in there. Now, this reads the shouldShowOnboarding, but how do we set this value? The most intuitive or the easiest way that you might want to do this is to pass our state into our OnboardingScreen composable. However, this is not what we want to do. We want to make sure that our composables are clean and like black boxes. So you shouldn't be putting in state that you then change from within that composable. Instead, what we do is the same as we did for the button. We actually pass in a callback. In our case, we can create a callback called onContinueClicked and give it that actual callback, which then would set our shouldShowOnboarding to false. Now, this actually isn't a parameter yet, so let's add it to our OnboardingScreen. So inside, we can say onContinueClicked, this is a function. So this is our way to define in Kotlin that this thing is a function. So we're passing a function and, in this case, a callback because this is being called from within this composable. And so this callback can actually be removed. And instead, we just forward that onContinueClicked here. And now, if we would run this up inside our emulator, we should be able to work with this. I do see that there is still an issue here. So in our preview, we're not passing anything for that onContinueClicked value. So let's pass something. Well, in our preview, we don't really want to act on this. Right? Like, nothing should change. We just want to show the OnboardingScreen, so we can pass an empty call back here. So I think that fixes it. And in the meantime, let's spin up that emulator again and see what this behaves like. So we are opening the app, so let's go all the way back to the top. We're opening MyApp. And inside MyApp, we have this state, which starts as true. We should be showing onboarding which means that this if else is being evaluated. And so instead of hiding things from our composition, we're actually just dynamically showing certain composables, yes or no. So we're adding composables to our composition., and we're removing them whenever we're done with them. So in this case, whenever this value changes, this if else statement is being re-evaluated. And if the value changes from true to false, then this will just not be called anymore, which means that the OnboardingScreen composable will leave the composition and, instead, the Greetings composable will be added. So let's see if that actually works. So in our emulator, when we press the Continue button, it calls the onContinueClicked callback, and there we go. It shows our Greetings composable. Now, in this case, we're using a basic Boolean value to work with this. Of course, in a bigger app, you might want to start working with identification library. But this is just to show you how your app can actually work with state on a higher level. FLORINA MUNTENESCU: Oh, actually, I want to point out one thing you've done here. So when you copy pasted the code for the onboarding, you also copy pasted a preview. And that's something that's really cool. If you switch back to the preview, we can actually have multiple previews in the same file. And actually, even for the same composable, we can have multiple previews. And to be honest, I find this so neat because you don't have to go all the way to the app and navigate to, I don't know, the Greetings screen to be able to see how that looks. You can just use the preview and, yeah, it's easier to debug your code. JOLANDA VERHOEF: Shall we add another one? FLORINA MUNTENESCU: Oh, yeah. JOLANDA VERHOEF: We can add another preview just on top of this one. So I'm just literally duplicating the first one. And for this one, for example, we can use-- winging this one because I'm not 100% sure. Oh, no, this showSystemUi. I mean, actually-- FLORINA MUNTENESCU: The dark uiMode. Right? JOLANDA VERHOEF: Yes. uiMode. There you go. So this should be uiMode yes. So we can actually show our dark theme inside here. And let's rebuild that and see what that looks like. So for the onboarding preview, I just added another preview annotation. And I'm adding the uiModes night, which means that we're actually showing the dark mode here. And let's minify this a little bit. There we go. FLORINA MUNTENESCU: I find this so neat. So we got some questions on the live chat. So some of them say that they're a little bit confused, that it looks like we don't have separation of concerns anymore. That's true for this codelab. Keep in mind that, here, we're just showing like really, really the first steps with using Compose, so it's normal. But we actually have more content that shows you separation of concerns and how to use state much more in depth. So check out our other codelabs and documentation. JOLANDA VERHOEF: Can I add one thing to that? FLORINA MUNTENESCU: Yes, please. JOLANDA VERHOEF: I think what I wanted to say is if you're being more advanced and if you're starting to create a bigger app, Compose is just a UI layer. Right? So there still will be few models, there will still be repositories, you will still build your Android app the way you were used to, but you're changing the UI layer. And you will be interacting with that view model in order to get your data and then show it in your UI layer. FLORINA MUNTENESCU: Someone else is saying that they're an Android Kotlin beginner, and they're in the process of completing a pathway from 2020 which uses XML for activities. And they're asking us if it's recommended to avoid the XML and use Compose instead. Well, truthfully, I think you'll see XML in apps for a long time, especially once you switch companies and so on. But I think it's a good way to learn both XML because probably apps will have to maintain for a while XML. But also keep an eye on Compose, which, as we can see, it's starting to be adopted quite fast. I think we already see tens of thousands of apps, or more than 10,000 apps, on Play Store using Compose. Actually, the Play Store itself uses Compose. OK. So we went through several things, but, Jolanda, how about showing a list with recycle before this. Right? JOLANDA VERHOEF: Yes, indeed. So we've been showing, so far we've been showing two greetings. Right? So we had just a Hello, World and the Hello, Compose. So let's spice things up a little bit, and let's make a list of 1,000 elements. And I'm going to warn you, if you're coding along, don't run this on an emulator yet because your computer might get a little bit hot. But let's code and see-- let's create a list of 1,000 elements. And we can use the list builder here. And we can say that for each one of those 1,000, we're going to create a string, in this case, just using it, which is the value of the index. So this will create a string with the number 0, with the number 1, 2, et cetera, all the way up to 999. And if we would run this down, then this for loop would start doing a lot. Right? So it would actually go through 1,000 values. And for all of those, create this Greeting composable. That's a little too much. Composables are cheap to create, but not that cheap. So we do want to be a little bit careful here. And so instead of using this for loop, we can actually move to something else, which is called a lazy column. So the word lazy already kind of maybe gives it away a little bit. The lazy column only creates those composables that are currently in the screen, so that are currently shown on-screen. So what we can do inside the lazy column, instead of directly creating those greetings in there, we can use the items call. There's also, you could create one item or you can create more than one. In our case, we want to create more than one. Right? So for each of our names, we want to create a greeting. So we can use items, Greeting here, and then there we go. We can say for each of our names, we will call that name, and then we will call the greeting for that. So this is actually using a DSL, which is a very complicated way of saying that this isn't directly calling composables, but it uses items or, if you want, item is also an option, or you can work with index items in here. And that's all tied to that lazy column. The same way, by the way, you could also create a lazy row. So let's run this on our emulator and see what that lazy column will do. So now it's safe again. You can run it just how you want. So let's see. Let's spin up our emulator and see what that list would look like. FLORINA MUNTENESCU: Oh, so no more recycle view, no more adaptor to write, I can actually be lazy? JOLANDA VERHOEF: You can be quite lazy. Yeah, that's true. So let's see if this actually works. Yeah, there we go. So we have a huge list here. And for each one of those, I can open, expand them, and I can collapse them again. So just to show you a little bit more of the strength of that, we can also say that we want to get some header here. So we will just say that we want to show a text which says header here. Or if we want to, we can actually even work with sticky headers, and there's quite some fancy functionality in here. And so just adding that item here with whatever composable you want inside will actually give us the ease of mind to work with that lazy column and add it to the top. So as you can see here now, we can see that there now is a header, and it just scrolls along with the rest. Isn't that amazing? FLORINA MUNTENESCU: Yeah. It's a little cool. Well, should we say how we persist state? JOLANDA VERHOEF: Yeah. Because you might have noticed already, that every time-- well, of course, every time I run the app again, it starts in the onboarding view. But actually, when we would rotate our screen or when we would move to dark modes-- so let's go into our list and let's, as a demonstration, go to dark mode instead. What it does, it will actually reload our activity. So that's something that happens if your configuration changes. What actually happens is that your activity will be reloaded. And currently, in our app, that leads to that composable being recalled again on the highest level and, thus, we're back in the original MyApp, which then instantiates the first time this mutableState to true, so we see the onboarding again. And that's not what we want. Right? If you rotate your screen or if you change to dark theme, then we want to be able to remember where we were. And this is actually quite easy. So we were using the remember keyword here. But instead, we can choose to remember Savable. And remember Savable is simply going to remember the value that's inside, but also contain it over configuration changes. So if we now run the app and we go into our list and then we rotate our screen or if we move back to light mode, then I would actually expect that to stay in the list and not go back to onboarding. So let's see that in action. Let's continue to the list. Let's scroll a little bit. And then let's change back into our light theme again. There we go. And there we go. We're still in our list. So this is just one change in a word here, and that made it survive configuration changes. FLORINA MUNTENESCU: OK. How about putting some life in our app and add some animations? JOLANDA VERHOEF: Oh, yes. Let's add animations. I think I would love to add an animation for our expanding action because our expanding action is now simply setting that value to 48, I think, and 0. But we can animate that value. So let's go into our greeting because that's why we were setting the expanded value. And inside our extraPadding, instead of just setting that to 48 or to 0, we can use an animation here. And there's all kinds of different obstructions for animations. In this case, we will be using quite a low level one, but it's still quite simple to understand. And we will use animateDpAsState. So this is doing something similar to what we were doing before. There we go. Wait, let me quickly finish this up. Nope. Almost, almost, almost. We, of course, have to set the target's value to this value. Sorry, I was so enthusiastic about all the blocks of code everywhere, but this just needs to be a regular round bracket. So the target value you set to 48 or 0 dp, but what actually happens is this animateDpAsState will return a state, which is the same as we had for our expanded value. And so we can also do the same with property delegation, so we can use by here. And that way now, what happens is that if you set this value, so if expanded changes from false to true, then this target value will change from 0 dp into 48 dp. And the animateDpAsState will start emitting new values in that state construct every single frame, I guess. And then that extraPadding will be updated. So it starts at 0, but then it will go to one, to two, et cetera, until it is at 48 dp. And every single time, our column will be updated with this new padding. And so if we run this, we should be able to see that now this expands with a nice animation. There's one small last thing we can do here as well is we can actually define an animation spec here. So you can quickly customize-- so there is a default animation spec which, in our app, we can see by pressing Show More, Show Less, as you can see this now, nicely animate. But you can also use an animation spec here. So for example, you can use a tween which goes linearly from one to the next. And we can set how long we want this to take, for example. So we can say the duration is 2,000 milliseconds, so now this will take two seconds to run. And this way there's different types of animation specs that you can work with. You can create easily like springs, or bounces, or however you want to animate your things. So now, it's a slow animation. FLORINA MUNTENESCU: Yeah. But honestly, saying that you wrote four lines of code to implement an animation and customize it, I really like it. I can be really lazy. Cool. Do we have anything more we could show for animations? JOLANDA VERHOEF: I think this is it. FLORINA MUNTENESCU: Cool. I think I'm afraid we'll have to wrap it up. If you switch the codelab, we can see that we have one more section that we didn't get to, like theming and then a few final touches. We promise styling and theming is easy, but we can't cover it in just three minutes. If you want to learn more about Compose, we have a Compose Pathway. Do we have the link at hand, Jolanda? JOLANDA VERHOEF: Yes, it's in the codelab, actually. FLORINA MUNTENESCU: Awesome. Great. Yeah. Check out the Compose Pathway. We have a lot of resources for you to get started with Compose. Similarly, we have a lot of documentation on our developer.android.com. And because I know, you want to try things more, we have another Compose, a Migrate to Compose Code-along tomorrow. And also tomorrow, we have an Ask Android focused on Compose and material view, so bring all of your compose questions to that Ask Android. And I think this is it for now. Thank you, everyone, for watching and for following along. JOLANDA VERHOEF: Thank you. Bye-bye. FLORINA MUNTENESCU: Bye. [MUSIC PLAYING]
Info
Channel: Android Developers
Views: 15,186
Rating: undefined out of 5
Keywords: Android Developer Summit, Android Dev Summit, Android Dev Summit 2021, Android developer, Android development, new in android, new in android development, type: Conference Talk (Full production), pr_pr: Android
Id: k3jvNqj4m08
Channel Id: undefined
Length: 58min 12sec (3492 seconds)
Published: Mon Nov 01 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.