[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]