[MUSIC PLAYING] ROMAIN GUY: Good morning,
and welcome to What's New in Jetpack in Compose. I don't know why it's called
that, because this is not really what the talk is about. It's more, what is
Jetpack Compose? Anyway, I'm Romain Guy. I manage the Android
Toolkit team at Google. CLARA BAYARRI:
I'm Clara Bayarri. I'm a tech lead on the toolkit. ADAM POWELL: And
I'm Adam Powell. And I am also a tech
lead on the toolkit. ROMAIN GUY: And the toolkit
now is also Jetpack Compose. My clicker. I need that. All right. So what is Jetpack Compose? The real title. So according to
developer.android.com, Jetpack Compose is a modern
toolkit for building native Android UIs. And it simplifies and
accelerates UI development. Blah, blah, blah. It's all in Kotlin. So instead of trying
to tell you what it is, I think it's more useful if
we try to tell you what kind of problems we're trying to
solve, what kind of goals we're trying to achieve
with Jetpack Compose. We did some of that. And look, it's me again. Same short. Same pants. Different watch. ADAM POWELL: Consistency. It's good. ROMAIN GUY: Yes. So Adam and Jim were
with me on stage and did most of the talking. So we explained some
of the reasoning. And I wanted to go
over other points. The first one for us is to
simplify the development. And we all got used
to the complexity of Android development. And the complexity can
mean many different things. Some of our APIs already
are awkward to use. You have two many different
files, different languages. So I want to show you
an actual example. So that's something
that Jim, who works on the Compose
compiler in runtime, has come up with to try to
convince a bunch of people internally that Compose
is a good thing. So the idea is to
build a simple app where you have a list of names. And you just want to type
something at the top. And it's going to
filter the list. So how would you do that
with the existing UI toolkit? You start with the
simple XML file. You're all used to that, a
recycler view with the text and the [INAUDIBLE],,
all that good stuff. Then you need another XML file
for each list item in the list. So we already have a
couple files in XML. Then you need your
filtered list view that's just going to do
that find [INAUDIBLE],, find delete text,
set up the listeners, find the recycler view,
set up the adapter. I'm not even showing
the activity here. Finally, you need
the adapter itself. So actually, it's funny. Because when I put that on
the slide, I'm like, wait, is that the best way we
have to filter a list? So I Googled it. And it turns out-- [LAUGHTER] Because I did write some
of the filtering APIs. So I thought there
was a better way. It turns out there's not really. And there's a lot of blog
posts, articles, and even Stack Overflow questions
on how to do this. I'm proud to announce that this
is one of the simpler ways. And then you can use
RxJava or fancy frameworks to make this happen. Anyway, that's a lot of code. It's annoying. It is not simple. So this is the entire
Compose version. All the code you need
is there on screen. Much easier. It's going to be a lot
easier to find bugs. And more importantly,
when you read the code, it's pretty obvious what
the intent of the code is, whoever read that code,
what they wanted to do. So you say, I want a scroller. I want a column of text fields. Sorry. I want a column with a
text field at the top. And for you each row I want
an imagined piece of text. And as soon as you change
the content of the list to contact list, everything
updates as you would expect. Another thing we want to
do is fix what's broken. So that's our API regrets. We talked about
this at Google I/O. I invite you to watch the
talk if you want to know more. We had a few funny anecdotes. One of the good
ones, of course, is View.Java that now clocks at
around 30,000 lines of code. Sure. There's a lot of comments
in there, but still-- ADAM POWELL: I think that's
down from some point. ROMAIN GUY: Yeah. But still, it's a lot. So the plan for
us is also we have a lot of baggage and history. So there are things
that we just cannot fix. We know there are problems,
but we just can't fix them. We could introduce
new bugs, or apps may be relying on
the current behavior. So we're kind of stuck. We can't really do much more
with the existing UI toolkit. We want to stop cheating. This is something
we did originally. And I think Adam
recently, you said on the Kotlin
[INAUDIBLE] Slack channel that you really, really
want add hide to disappear. So if you ever looked at the
source code of the Android platform, we have this magical
annotation called add hide, which turns public APIs
into very private APIs. And super useful to us. It was a way to work around some
of the limitations of the Java programming language. But it led, over the
years, to an issue for us. For instance, let's say you want
to do something with text view. That text view doesn't
do well out of the box. So you have your sensors. Write your own text view. Well, you start
pulling that thread. And then you're grabbing
the entire text package. And you're running two priority
APIs all over the place, and you can't do
anything about it. So that's an issue for us. And it's an issue for us. So what we want
to do with Compose is we don't want to rely
on private APIs anymore. We kind of have to
at the low level to interact with the platform. But all of our widgets
are built entirely using public APIs that
are available to you. And I'm going to show you
actually an example of that. ADAM POWELL: And
the good news is that it's kind of forming a
forcing function for us to open more things in the platform
itself along the way. ROMAIN GUY: And we have to
think a little harder about what we open. Material and animations. Oh, I had a pretty heart
emoji on the slide. It's gone. ADAM POWELL: Aw. ROMAIN GUY: I like hearts. Anyway, so now we're in
the Material Design world. The existing UI toolkit was
built before Material Design was a thing. So the good news now, we
can take Material Design into account. This is what Clara and
her team do in London. And we also are
building animations in strong collaboration with the
rest of the Compose UI toolkit. So we expect it's going to
be easier and more powerful than it was in the existing one. Tools plus Compose. Same thing. When we built the initial UI
toolkit, the existing one, there was not that much
interaction with the tools team because the tools
team was very small. So a lot of the tools
came after the fact. So the visual editor,
the [INAUDIBLE] that you saw in the keynote. And we want to change that. And you just saw that
we're building things like live previews that are
composable right now, as we're building Compose itself. We adopted Kotlin. We announced that we
want to go Kotlin first. So that would be
one thing for us to tell you to go Kotlin first. And we thought that
maybe it was a good idea to do what we say
that you should do. So Compose is
returning to Kotlin. And Kotlin actually
unlocks a lot of the things that we do in Compose. The way that we design the API
is possible thanks to Kotlin. We rely heavily on
lambdas, for instance. And we plan on using fun
features like co-routines. Adam is chomping at the
bits to add co-routines all over Compose. And finally,
compatibility is something that we deeply care about. We are not asking you to
rewrite your entire app. If you want to adopt Compose,
once it's ready for production, we don't expect you to
just delete all the code, like we did for Instant Run
and to start from scratch. We want you to write
your next activity, or your next fragment,
or to be able to reuse some of your custom views. And chances are that some
of the existing views, we won't even rewrite ourselves. So things like surface
view or web view. If any of you wants to
write a new web view, please come help us. But we're not going to do that. So we're going to use
the existing [INAUDIBLE].. All right. And finally, this is what the
technical stack of Compose looks like. So on the one side, we
have the build time stack. So that's on the host
that runs on your machine. So we use a slightly modified
version of the Kotlin compiler. So we work closely
with JetBrains. We just added a couple compiler
extensions in collaboration with them. And we use those extensions
to enable our Compose compiler plugin. So it's funny, because
last week people saw that we had the artifacts
on Maven for Compose. And people started using them. And they're like, oh, great. It works. We're like, no, it doesn't work. You need the compiler that
we haven't released yet. Like no, it works. The reason it seems to work
is because the syntax is just pure Kotlin. So it's just invoking functions. It was just not very reactive at
all, which is kind of a bummer. Anyway, and so we also
have Android Studio. And then on the runtime side,
so Compose at the bottom is a runtime that
just deals with trees. It knows how to produce trees. It doesn't know
anything but UI toolkit. So we could reuse it to
do other things than UI. Then we have the UI
core, the UI foundations, and, finally, on
top, all the widgets. So that's pretty much it. And now Clara will tell you
more about how Compose actually works when you write code. CLARA BAYARRI: Thank you. [APPLAUSE] Sorry. Get your applause. I want to walk you
through some of the things that you need to
understand to really be able to play with Compose,
some of the basic concepts. The first one is
composable functions. I know Romain has kind of thrown
this term out a few times. But a composable function
is just a function. It's a function that
takes some input, and just decides what UI to
show based on that input. So in a way it transforms
your application data into your UI hierarchy. Creating a composable
function is very, very easy. So, for example, we have
this greeting composable. It takes a name as a string. And it just decides
to emit a text element that uses that string. Text is one of the components
that we will provide for you. It is the equivalent
of a text view. So it shows a text
on the screen. The interesting thing here is
because we have the runtime and the compiler behind us,
whenever you change that input-- so say the name you're
going to pass through changes-- well, then we take
care of figuring out what changed,
re-invoke the function, and figure out what we need
to change in the UI hierarchy for you so at any point
you are just describing what you want to see. And we'll take care of the rest. Say, for example, we want
to build a news story. We can actually omit
several composables within a composable function. So we can omit, say, a
title, subtitle, and a date. And we're just going to
build our news story. This is absolutely fine. But you will find
you kind of need to have an opinion on how
you want your UI to show. Because if you just do
this, you will end up with something like this, which
is not really what you wanted. So this is where concept
number two, layouts, come in. We are building out a
bunch of layouts for you out of the box to make sure
that you can place things on screen as much as you want. So, for example, we can wrap
those three texts in a column. Column is the equivalent of
a vertical linear layout. So we'll just place everything
one on top of the other. So then suddenly this looks more
like what we were expecting. Right? So we have our title,
subtitle, and date. Still, Android
developers are used to being able to
customize a lot of things. You'll be like, ah,
you know, this is nice, but my text is really stuck
to the side of the screen. So we can go a bit
further, add some spacing. And what that will do is just
create the spacing around it that we need to make
it look a bit better. So until now, we've
built this entire UI just as like one function. Right? Going a bit further, column
doesn't just take text. I know I used three
text as an example. But we can throw
anything in there. So, for example, we can
just display an image by using an image
composable, and then build up our UI in this way. Huh. Oh, yes. Sorry. We're providing a bunch
of layouts out of the box. There you go. We have row, which will
place things horizontally. We have column, which will
place things vertically. Those are easy to understand. We have more advanced things. For example, we've
introduced flex, which I believe is new to Android. We're building a bunch more. There's a bunch more in there
that you can go and play with. And we are working
on ConstraintLayout. But it's just not there yet. So look forward to that coming. And then, well,
we've built a layout, but really what you want is for
us to build components for you. Right? So Compose comes
with a bunch of built in components that should
make it really easy for you to get kickstarted. First of all, we've
partnered very heavily with the Material team. Because we want to make sure
Material is out of the box just there for you. So we started creating
the Material components. They're not all there yet. But the team has already
been hard at work to be able create those beautiful UIs. And we already have things
like buttons, checkboxes, and a bit more complex things. App bars, drawers. Some of these pieces
are in their influx. We'll get there. But you already have
the basic pieces to build something
like a sample app. So say we want to build
this Material card, building up on the example
we had before with the image and the three texts. Well, we'll take the
code we've had before. I haven't changed anything. We wrap it in a card. And we can give a
card a shape, cause this is a material thing
that you can choose what your corner should look like. So, for example, I want
rounded corners on my card. We can style the text to look
prettier than it was before with the three same styles. And you'll say, here you're
reaching into a theme text style. This looks a bit weird. So let's take a step back
to the Material spec. So in the Material spec
there's a definition of all the typographies that
you could define in your app. So how you define all the
styling of your entire app. There's a pre-set amount of
textiles that you could use, just like they have a color
system and a shape system. We've built this out
of the box for you. So if you buy into
the Material system we have a Material
theme composable that allows you to configure
all of the colors, all of the typography, all of the
shapes you need for your app. And then when you put
this in your hierarchy, any other Material
component you're using will know how to read all these
values by default so you don't have to actually style them. Similarly, you can
reach into these values. And that's what I'm
doing in this example. So I'm saying, out
of these three texts, I'm reaching into
the style and saying I want H6, subtitle
one, and body one. It's a very easy way to style if
you've set up all your styling for your app correctly. And finally, to
finish the app, I can add a row with
a couple of buttons. Again, we've provided the
material implementations of buttons. So material defines three
different kinds of buttons. There is contained,
outline, and text buttons. And you will find that we
keep the same terminology to make it easy to find things. So we have contained
buttons, outline buttons, and text buttons available. So all of the
Material components we provide we've built purely
on the public API from layers below us. So Compose UI core provides
some foundation for us. And we've built everything
layered up on top of that. So we've tried to not cheat
with any private APIs. This means that Materials
should be a showcase of how you can build components. We're hoping they're samples. Right? They're very useful components. But at the same time,
it should be code that you can go in, read,
and see how we've done it, and take those
examples for yourself. We've also tried to
break everything down into multiple layers, because
you can break everything into more and more
functions, to make sure there's as many
reusable bits in there as possible for everyone. And as part of that, we've
created this foundation level in the middle. The main idea there is, there's
a lot of material that is not necessarily design specific. There'll be a bunch of gestures,
accessibility that we've implemented to do a button. So we can put the actual
button in Material. But then we can
put the internals of the button in foundation. So all the gestures,
accessibility, other things that we've
built that are not UI so that you don't have
to start from scratch. You can already
start from that layer and just build your UI on top. And then no matter how many
components we give you, there's always going
to be custom views that you'll want to do. There's always going to
be that designer that asks for a very strange thing
that we had never anticipated. So in the existing toolkit,
you have to extend a view. You have to take care
of a bunch of things. It's a lot of work to
create a custom view. In Compose, it's
extremely simple. You just have to
create a function. And there's some
building blocks that you can do to help you build
up that custom view. The two basic ones
are draw and layout. So draw will give you a handle
into doing any kind of custom coding that you want. Say, for example, we
want to do a checkbox. And a checkbox is just
a square with rounded corners and then a checkmark. Right? So let me walk you through
the code to do this, cause it's quite simple. To draw the box,
we open a draw tag. We get access to a
canvas and the sizes we've been measured to. So we can easily
calculate, how much do we want our square to be? Apply the rounded corners,
and then just draw the square. Same for the checkmark. We'll open a draw. We have access to the
canvas and the sides. We can calculate the three
points that make a checkmark, and then just draw
those on the screen. So surprisingly simple. The next part is layout. Of course, we will try to
provide as many layouts as we can out of the box. There will be always
something that you want to do that is extremely custom. Well, you can reach down
into the layout component. I've tried to show
here an example. The most simple
example I could find is a stack or a frame layout. Right? Like stacking things
one on top of the other. So you will create a function
that takes a bunch of children that it needs to place. We open a layout tag. We get access to
these children that are measurable pieces, all
the constraints we have. And then we just have to
go through two easy steps. We measure each of the
children to make sure we know what size they want to be. And then we decide what
size we want to be. So, for example, in
a stack I'll probably want the maximum of the children
to make sure they all fit. And then I'll place them
each at the 0, 0 coordinate, cause that's what
a stack would do. So then we'll end
up with an effect that is something like this. Hand it over to Romain. ROMAIN GUY: All right. So what do we have today? Like I mentioned in
the keynote, we just released our developer preview. It's called 0.1.0-dev02. So if it's not clear
enough, it is not quite ready for production. But I want to show you
some code and a demo. So if you can switch to
the demo machine, please. All right. So one of the simple apps
we have is called JetNews. You can see it on the
right in the emulator. So it gives you a good
idea of the kind of things you can already build today. We have horizontal scrolling
lists, vertical scrolling lists. The tech stack is
already pretty advanced. So you can do a lot of complex
text layouting and styling. So this is a very
interesting app to look at. It's not a real app. It's not connected to a
database or web service to keep the sample
simple, but it's a very good example of how
to create your own widgets and to use the existing widgets. So what I wanted to show
you is what you see here. So yesterday I built this
little to-do list app. So I've got the old [INAUDIBLE]
icon in the bottom right. And when you click on it, I
just want to add list items. And each list item will
have a little bookmark icon that I want to be
able to toggle. So the way it works
here, [INAUDIBLE] is a simple data
class in Kotlin. Is the text big
enough, by the way, on the screen for everyone? All right. I'll assume it's a yes. So we have a data class. It's immutable. Everything's a vowel. And you can see that night time
can be a favorite, and just a description and an ID. Then the actual
model for the app is just this empty
list of to-do items. And it's going to be
an immutable list. And I wrapped it
into this object. That's probably not how you
would do it in a real app, like using an object like
this as a global object. But I used this at
@Model annotation. And an at @Model basically
turns this object into an observable object. So whenever I change
the item's property, the UI will re-compose
automatically. And Adam will tell
you more about this. And if you want to
know even more details, Dylan has a talk tomorrow
where he explains how it works internally in the compiler. This is the main part
of the application. So very similar to the
way we saw this earlier in the keynote. I have a vertical scroller. I have a column. I just iterate
over all the items. And then for each
item, I create a row. You can ignore the
layout parameters. But I have this bookmark icon. So this is a
composable I created. And one of the things I
really like about Compose-- and that was one of the
things we really wanted to do. We wanted to make it easy
to create your own widgets. Very often in
Android applications we end up with very
large XML files, because it's very cumbersome
to create all those XML files, doing the [INAUDIBLE],, doing
the includes, and the merge, and all that stuff. And here all you have
to do is when I first wrote this application,
I wrote everything into these items composable. And then when I saw that, I had
this bookmark icon composable. I just selected it. I extracted a method using
a refactor in IntelliJ. And then I just added my
add composable annotation. And that's all you need to
do to create a new component. You don't have to extend from
view of your group in override methods. And then I just have a
small spacer, and finally the piece of text that
describes the item. So the bookmark icon is here. And here you can see
another interesting-- oops. I'm revealing what
I'm going to type. That's my cheat in case
I forget what to do. So my bookmark icon
is pretty simple. It takes one of
those to-do items. It's read by a clickable. So that's how you create
a click listener really. And inside all I do
is, I have this branch, and I say, if this
item is a favorite, then I want to show the
image that corresponds to the favorite state. And if it's not a favorite, I
want to use the other image. So it shows you
that it's not just about using four loops,
one instance for each. You can use any kind of
control flow you want. And it makes the code
extremely easy to read. And you could do this piece
of code in different ways. Anyway, so I have two
callbacks in the app. I have this toggle
favorite, when we're going to click
the bookmark icon. And I also have one when
we click the [INAUDIBLE].. So I'm going to
try to write them. So when we add an
item, I'm going to take the list
of items, and I'm going to add a new item
that I conveniently have in my clipboard. So it will just give you a
default string and an ID. So if I rerun that-- And you can note,
that's all I did. Right? There's no listener. I don't call re-layout, request
layout, or invalidate it. And when I click the button, a
new item appears in the list. And that's all you have to do. And that's thanks
to this @Model right here, because we observe
that the items are changing. But now I want to be able to
toggle those little bookmarks. Like when I click right
now, nothing happens. So the way I'm doing
it is because it's an immutable list-- Let's see if I remember. So I'm going to do a map. I feel so fancy when I do
functional programming. Let's see. Oops. It.ID equals the
item we clicked. ID. That will return a copy. And I will do favorites
equals not todo.favorites. So I'm just toggling
the Boolean. Otherwise it'll just
leave the item unmodified. All right. That should work. Relaunch the app. So add a few items. And now when I click,
it toggles the icon. Yay! Ooh. Hm. [GROANS] And that's why
always be prepared. All right. I will make it work. Oh, yeah. I used the wrong item. All right. Add items. Bookmark. Yes. All right. Now it works. So again, the point
here is to show you how easy it is to
manipulate your data and to build that logic, and
make it interact with the UI. And more importantly, you can
keep both kind of separated, or more separated
than you could before. But Adam will tell you
a lot more about that. Can you go back to
the slides, please? ADAM POWELL: All right. So this is the part of the talk
where we talk a little bit more about kind of our design
thinking behind Compose, and kind of what's on our mind. So in addition to the team's
ongoing work with Compose, we've also been
spending a lot of time talking with the community. Some of you, have probably
been part of that conversation. As well as running some
more formal user studies. And so here are some of
the things that are really sort of top of mind for us
that we're excited about or that we think is really kind
of important as you take it for a test drive. So be careful that some of
this isn't implemented yet. This is just kind of how
we're thinking about some of these open questions. So please join us
in participating in some of the design process. So the first thing that
really kind of hits you as soon as you start working
with Compose is the data flow. And while some of the tools
available for managing that data flow came
into Compose very early, or they existed
before Compose did in the case of just some of
the standard functional and reactive systems that
you might be used to, the best practices and teaching
materials around all of this is still kind of a
work in progress. Like we really are building
something new here. So the Android architecture
components libraries have been really
successful so far. And it set the stage nicely,
and established patterns that we've continued
with in Compose itself. So let's go ahead and distill
down some of those ideas and kind of see
where they lead us. Next. There we go. So we showed this
diagram at Google I/O during our talk
on compose there. Dataflow is really the first
thing that you encounter, like I said. The shape of the API more or
less enforces this top down, one way data flow approach. It's just functions
calling functions. And most people
like this so far. So it's always good to have
a single source of truth in your UI. And it's kind of
frustrating when your views have a different idea
about the state of your app than your application model
does, which is definitely the case when you're
working with views in the average Android app. So what do we mean when we talk
about single source of truth in a UI? Well, it's about avoiding
conflicts or making sure that you're not showing
inconsistent data. But it's also kind of about
who has write permissions to that state. So with Compose you
prevent inconsistencies by having the owner
of a piece of state pass that state to the
other composable functions that need it. So the parameters that we call
our composable functions with, they have to come
from somewhere. And the source of that data is
kind of our source of truth, by definition. And the interface that we use
to pass those parameters really kind of determines what
the composable functions can do with it, just like
with any other function. So you see access control
layering like this in Kotlin frequently in the
standard library. You've got the list
interface here, which only defines
the read methods. But the methods to
actually change that state are defined in mutable list. So even if I have
a mutable list, if I pass it to a function like
the aptly named do stuff over here as the read
only list interface, I can be reasonably sure that
do stuff isn't going to change my data out from under me. So from a certain point
of view, source of truth has to do with who has
access to a mutable reference to that source of truth,
and what kind of mutations a caller can perform
with that reference. If you can change the truth
in any way that you want, are you yourself a source of it? Well, kind of. So we use event handler
lambdas kind of liberally throughout Compose, because it's
kind of a more restricted way to express a very specific
mutation that you can make to state without actually giving
away the keys in the process. So here's kind of
what I mean by that. We can show this just by using
views with some of the APIs that you're used to. So here I've just added a
click listener of the view. And inside that listener,
I'm using the fact that I have access to
this myList variable from the example before. And that's our mutable list. So that's in my lexical scope. So I can capture it as
part of a listener block and just use it directly. I haven't actually leaked
a mutable reference outside of this code snippet. The block where this
appears is the only place where mutable lists is
accessible from anything. I haven't permitted mutation in
any sort of uncontrolled ways by giving out a reference to it. So all my view can do is just
tell me that a click happened. And I've defined how
this higher level will respond to those clicks,
given this layer's access level. So this is a really
simple example. And we're just kind
of using lists. And we definitely did
some in the demo as well. It's sort of a hand
wave for, hey, this is the rest of the @Model. I mean, clearly you'll be doing
something a little bit more sophisticated than direct
mutations on lists. So bear with us a little bit. So a more real example might
do something kind of like this. So in this sort of sample
view binding method, I'm setting a click listener
and make some requests to my app based
on the parameters that I was passed to
when the bind happened. So just like before
with the mutable list, I didn't teach buttons how
to add and remove bookmarks. I defined what a
bound button should do when the button
raises a click event. And I used the context
that was available to me when I bound it-- that's
just the lexical scope-- to form that definition. So this is all pretty
standard stuff. So what's actually new here? I mean, this isn't
unique to Compose here. But it's kind of
the framing for what comes next, which is that
Compose code is really, really dense. We think that this
is pretty great that you can remove
so much boilerplate, but the trade off for that is
that sometimes the distinctions of what you're really
doing become kind of subtle when it's compressed
into such a small space. So let's look at
the same pattern that we just saw using Compose. In this case, it's not
actually that much smaller. It doesn't look
that much different. We're declaring
the actual button as an implementation detail,
along with its properties. So what its text is and what'll
happen when it's clicked. Same thing. But the syntax here is
kind of interesting. We said before that state
flows down and events flow up. But we're passing both text
and the onClick handler to button in the same way. They're both parameters. So in a way, event handlers
are just another kind of state that flows down. It's a state that the
bookmark button created and that it owns. So as far as button
is concerned, it's just this opaque
token that can be invoked. So when it is clicked,
we make a request to this bookmark or object
that's not yet defined here to please add or remove
the associated bookmark in response. So the bookmarker might be
backed by a local database. API calls to some back end. Really whatever. I can fake it or test it
outside the context of my UI. But I've defined
how the UI itself interacts with the
bookmarker in one place here, along with the structure of the
UI that's being manipulated. So bookmarker can be written to
expose whatever operations that are appropriate to its
clients like this one. So the single source of
truth here is preserved. So that's a single
snapshot of that truth. But the real power of a
declarative UI framework like Compose is that it can
keep up with changes over time. So it'd be really nice
if my code was always this straightforward, even
when time is involved, and when changes over
time is involved. So we saw a little bit of
that in the demo so far. So if you want to update the
UI in response to changes, well, you need your
data to be observable. And Compose is ready for that. We saw in the demo that when
you annotate a class with @Model that Compose will know
when its properties change. But why did we add
something new here? The Android ecosystem has a
lot of existing observable constructs. So what gains do we
get from this that justify adding something new? Well, we think that the answer
lies somewhere between the ease of understanding
of the kind of code that we can write this
way, and also just kind of the ergonomics of
actually using it in practice. So let's talk about
live data for a moment. Because it really has some neat
properties for building UI. Fundamentally, live data
is a single value holder. It'll tell you
when it's updated. So for our purposes in a UI,
only the latest value matters. Live data will
conflate its values and only keeps the latest one. There's no such thing
as missing an event. You don't actually
have an event streamed. There's only the latest state. And @Model behaves much in
the same way in this regard. So let's take another
look at an example, kind of like the bookmarks
code from before. If you've worked with Android
architecture components, view model, and live
data, you've probably seen something like this before. The view model sort
of becomes this hub for a bunch of different
observable data, along with some operations
to request some changes to that data. So we can reflect the same
sort of structure with @Model, but you can see something
kind of different here. So the hasBookmark method
that we used in the example before, it isn't
returning a mapped live data that has to be
individually observed. It's just accessing the
internal object's state. The list of bookmarks
itself isn't even public. @Model has turned
any normal reads of the object's properties that
happen within a special scope into observable subscriptions
even if that read happens layers down
the call stack. If I called hasBookmark through
some other helper function that I extracted in a
refactoring or something like that, even in a
completely different class, this would still work. We don't have to
turn it into a stream or live data mappings
each step of the way. So if we take a look back at
our bookmark button from before, we see that our UI
can be expressed in terms of these snapshots
of state and time. And Compose can help manage
the complexity of wiring these subscriptions
together for us, even if the actual
@Model objects might be several layers away
across the call stack or across an object
reference chain. So Compose will automatically
insert these observable scopes for us in composable functions. We don't even have
to think about it. So let's go ahead
and recap on that. So we need observable data. Compose will add @Model
classes as a new tool that we have available. But the cool thing
about it is that it allows for the sort of
cross cutting observability. So we can write less plumbing
and subscription management code in our apps. But the thing is is that
all these other observable and reactive systems are great. You don't have to
leave them behind if they're a natural fit
for what you're doing, especially if it makes
sense in other layers of your application. So if it makes more
sense for the bookmarker at this particular
layer that I've defined it to return a live
data or flow, or something from hasBookmark,
then we can still support automated but explicit
subscription management by using an effect
function, kind of like this. One more. There we go. So that's kind of the tour
of the background behind some of that. So really what's left
for us around data flow is kind of documenting
recommendations for the question
that's kind of left that you're probably
all asking yourself, which is which of these
tools should I use when. So kind of establishing some
of these guidelines and best practices are still kind
of a work in progress here. So more things that
are a work in progress. I hear a lot of
questions about how to work with
resources in Compose, and kind of what we
plan to do there. So let's talk a little
bit about that too. Again, this is current
thinking, subject to change. But this is kind of where our
heads are at at the moment. So when it comes to using
resources and the UI toolkit, we kind of think
that we've learned a few things along the way. And this is kind of a big one. So really we need to
avoid too many overloads. Some of you might recognize
where these are from. We have a lot of
ways to set an image. ROMAIN GUY: It's a convenience. ADAM POWELL: It's
very convenient. But the thing is
is that this makes the view in question
huge in terms of its own implementation. But even worse,
it means anything that tries to wrap
or reuse one of these in a composable fashion has to
reflect the whole capabilities of the API surface. We don't want this. So it's annoying
for us to maintain. And it's even more annoying
for anyone who wants to build something on top. Now, there are a
lot of reasons why you might want to
offer overloads for your own
composable functions. And we would really be
doing you a disservice by asking you to multiply that
by the number of overloads that you might need to cover
for this form of completeness because some underlying
piece that you're relying on offered all of these different
ways of doing a single thing. So that's something that
we'd kind of like to avoid. So we've also seen a
lot of other libraries appear to fill the
gaps in between what Android views offer and
what you need in your app. So image loading libraries are
a really great example of this. There's a bunch of
great ones out there. And they apply different
loading and caching policies for images, tie into
lifecycle, so on and so forth. But really this is because a
lot of resources are dynamic. They might come from the web. They might come
from a disk cache or plenty of other places. And tying into the appropriate
lifecycle events as signals, it's really not always fun. There's a lot to get right. So by focusing so much on static
resources in what we offered, we kind of created this
privileged type of data. It created a blind
spot for us when we were looking at the system
kind of from the inside. And it should be much more
straightforward to write these dynamic resource
policy layers. And static resources should
have to play by the same rules. So using the same public
API services to operate. So co-routines are awesome. Romain mentioned
sometimes I won't shut up. ROMAIN GUY: Yeah. You never shut up about it. You're not the only one. ADAM POWELL: There are more. So we still have a
few issues to work out with the experimental
compiler around generating suspending code. So we haven't done a
whole lot here yet. But our intention is to
standardize on co-routines as our async primitive for
working with resource loading, whether that's
static or dynamic. So Android's R class is
also pretty convenient. Having a bunch of
symbols that'll autocomplete for the resources
in your app is pretty nice. We'd like to keep that
sort of convenience, but really all of them being
ints is not so convenient. ROMAIN GUY: And I ran
into that yesterday while building the demo. I would get a crash, because
I had the PNG files and an XML file with the same name. So the runtime would
pick the XML file and try to load it as a bitmap. And blah. ADAM POWELL: Yeah. You had some fun with that. ROMAIN GUY: It took us five
minutes to figure it out. And we wrote the
god damned thing. [LAUGHTER] ADAM POWELL: All right. I think that we might be able to
squeeze one more into this list if someone will get
out of the way there. OK. So layering is a
pretty great idea too. Because once we start
talking about type safety and resources, and
avoiding overloads, the natural question comes
up, why should any of this be specific to Compose? We're really kind of looking at
how working with APK resources might be made better whether
you're using Compose or not. And while we're talking
about things that are great, whether you're using
Compose or not, let's go ahead and talk a little
bit about view compatibility. So we like Kotlin itself so
much because you can adopt it in an existing project at
your own pace incrementally, just a little bit at a
time, one class at a time, one test at a time, et cetera. So we're designing Compose
to be used in the same way. So if you have an
Android app today, you probably have a lot of
views with layouts, maybe even fragments that you've
extensively tested. So they work. Even once Compose graduates
to a stable release, rewriting your existing and working
code upfront probably doesn't sound so great. So you don't have to. So let's take a look at
a couple of the things that we have in
mind here, and how this might look once we spend
a little bit more time here. So the first step
in interop for us is being able to use just
a little bit of Compose inside an existing app. The API for this is about
as simple as it gets. It's just one annotation. And this may look familiar
if you saw our talk from I/O earlier that
you'll be able to annotate a composable function. It'll ask the Compose compiler
to generate a view subclass for you. This'll host an instance of
that composable function, with appropriate setters,
so on and so forth that you can use to feed
it data in the same way that you would via
its parameters. So this will let you use
Compose alongside your existing view-based UI. So this still isn't
implemented yet, but we're pretty confident
that this is the way to go. And that's kind of the
way that things always go. Right? It's the things that you're
most sure about are the things that you do last. ROMAIN GUY: Well, someone
has started working on this. It's just not finished. ADAM POWELL: Right. So the next step is a
little bit less certain. And this is something
that we're going to be doing some prototyping on. And you can follow
along with us in AOSP as we kind of play with
some different ideas. So even if you dive
into Compose head first, you'll probably still need
to use views within that UI at some point. Maybe you're integrating
a third party SDK that doesn't offer
Compose integration yet. You still want to
be able to use it. So one idea that we've had is
to leverage the new view binding feature that you might have
seen in recent Android Studio builds. So if you're not
familiar with it, if you have a layout XML file
that looks something like this, view binding will
generate some code that lets you consume it like this. So you get a type safe
object generated for you that you can use to bind values
and listeners to it in code. So you could imagine that
turning into something maybe like this when you
use it from compose. While this might be nice for
giving life to some layouts that you've already setup, it
implies some additional work if you just want to
use one custom view. Another idea is that we
could use the composition system itself to directly
manage a view hierarchy. The Compose compiler
and composition runtime were originally designed
to manipulate the view tree directly. So here, rather than
reusing layouts, you could use any
existing view subclass as if it were native
to Compose UI. But a limitation
that this has is that it might make things
look a little bit too similar. There's really a lot of
mismatches at the boundaries. And some things, like,
most notably, edit text, comes along with
a lot of gotchas when you're trying to use it
in this sort of reactive style, where the single source of truth
lives outside of that view. So the existing
view implementations still fundamentally
want to own their state. And layout contracts
work differently, so on and so forth. So we're trying to figure
out what the right balance is with some of these things. So this, like some of the other
design spaces that we just talked about, are
going to be occupying a good bit of our time
over the coming weeks. So we want to be sure that
interop with your existing code is as straightforward as we can
make it, while not leading you down a path that ends up
creating additional work if your constraints change. So we'll keep you updated
as development continues, but hopefully by now you're
asking another question. CLARA BAYARRI: So you've
heard what we can do. You've heard what we're
trying to get to do. But how do you play with it? Well, today we've published
a brand new website. So d.android.com/jetpackcompose. We've updated the content in
the Jetpack Compose website, including, for example,
how to file bugs. So please, go check that out. As part of that content
we've published a tutorial. And the tutorial will guide you
through the basic principles of Compose, kind of similar
to what we've done today, but much more in detail. So please go check that out. You can read through it
and it will make sense. Or you could go and download
the new Android Studio build that supports Jetpack Compose. It should be up soon I think. [COUGHS] ROMAIN GUY: Sorry. [LAUGHTER] CLARA BAYARRI: You will
also get a new template-- ADAM POWELL: He's
uploading it now. CLARA BAYARRI: --for
a Compose activity that will set up all your
dependencies and everything. So you don't need to go
read random blog posts that will tell you how to set it up. You can try the new preview that
we showcased at the keynote. You'll be able to preview what
you're building with Compose. You'll be able to
see the sample. So we published
JetNews as our sample. It showcased everything
that is there now and how you can use it to
build this news article app. And then other
things that you can try while you're here
at the Dev Summit, there's two codelab
sessions happening. The codelab is also available
online if you're streaming. And we have two talks that
you should go check out. What's New in Android
Studio, where Tor and Ja will walk through the tools
that we're building for Compose as part of that. And tomorrow we have
a talk from Leland, who's in our compiler
and runtime team. And will explain a lot more
on how the insides of Compose work, and kind of
demystify the compiler, and what we're doing
behind the scenes. Thank you very much. ROMAIN GUY: Thanks. ADAM POWELL: Thanks. [APPLAUSE] [MUSIC PLAYING]