ROMAIN GUY: Good
afternoon, everyone. I'm all Romain Guy, ADAM POWELL: I'm Adam Powell. JIM SPROCH: And I'm Jim. ROMAIN GUY: All right, and we're
here to talk about UI patterns. Funny, a lot of you are
interested in that suddenly. So there was a stealth
title for the talk, so obviously it's about Jetpack
Compose on [INAUDIBLE] UI toolkit that we're
working on, and that's available in open source. And so we want to talk a
little more about this today. But first we want to talk
a little bit of history and explaining why
we're doing this. So how did we get here? A lot of you obviously
are familiar with views and activities and fragment, and
I'm sure you love those APIs, they're close to perfect. So just to give you a
little bit of background. So this is a diagram that
Chad did for a talk that we gave, I think, last year. Shows you a timeline of what
we've been up to, especially on the Android Toolkit team. So after we shipped
Android 1.0, we didn't do anything
for many, many years. And then we finally
woke up and decided to write new
libraries to help you. So things like Recycler
View, Constraint Layouts, Arch Components, and of course,
we had the adoption of Kotlin. So we have been
working on new things, but one things we
one thing we never changed was the
UI toolkit itself. We did a lot of work
underneath to make it better, we had hardware acceleration,
we fixed some bugs, we added some bugs, we
added a few features, but we never changed
it fundamentally. So one of the things that
happened over 10 years ago was this. This is a device that
we call the Sooner, and this was the
first device that I used to write Android
code when I joined Google. So to give you an
idea, this device had a single CPU
running at 80 megahertz. It didn't have a GPU. We had 64 megs of RAM, and
the screen was 320 by 240. This was not the first
device that we shipped. We shipped the G1, which
was slightly better, but that was similar in spirit. We shipped it with the GPU. We mostly didn't use it. So [INAUDIBLE] toolkit
was pretty much design for this device. Fast forward a few
years, and now we have this kind of devices. So this is a Pixel 2, I believe,
and the screen resolutions now are 2160 by 1080 or 2960
by 4040 on the XL variants. And here you can see a
screenshot of the Sooner at the same scale, and
it fits comfortably over just one icon
on the launcher. So we're still using
the same UI toolkit, so maybe it was time
to rethink our approach to UIs to better
serve those devices. And on top of that, so we build
the architecture components over the past couple years,
and the approach we took was extremely interesting. We were more open. We did a lot of alphas
and betas and RCs. We talked with you,
the community, a lot to hear what you wanted from us. I mean, you build
the applications. We don't really anymore,
so we need your input to know what it is you
need, what is you want, what are the problems that
you're facing every day. So, yeah, what are
you asking for? And what would
you like us to do? So when we ask this
question for the UI toolkit, in particular, one
of the answers we got was unbundle the UI toolkit. Because until now,
every time we wanted to ship a fix in the UI
toolkit, or every time we went to ship
a new feature, we could do this easily
ourselves, but then you had to wait for just
a few years to be able to raise your
main API level. And I know I've had many
discussions personally with some of you where I said,
I fixed your bug, you're like, yeah, I don't care
because the fix is not going to reach my users'
devices for a long, long time. So, not helpful at all. So we are thinking about moving
some of our widgets to Jetpack, make them be a support library. Let's take text view, move
it entirely in Jetpack, and then we'll be
able to deliver fixes. But as we were thinking
about doing this we thought, well,
if we're going to do this, how about
we start improving those widgets a little bit. What could we do to
improve the APIs? And I'm sure you all love
the APIs of our UI toolkit, but as Chad put it, API design
is building future regrets. And we have a lot of regrets-- a lot, even more than you do. Here are some examples. So for instance view.java,
do you know how big it is? I'm sure you have big
files in your source code, I'm pretty sure you don't
have files as big as this one. So we have almost 30,000 lines
of code in view.java, yeah. [APPLAUSE] That is Google's
[? scale ?] right here. To be fair, it's
a lot of comments but still this is
not very good design. So that's one thing. And then there's random stuff. For instance, Spinner. Why do we have this
class called Spinner? It's a combo box or dropdown. We don't like Spinner, nobody
does, and a bunch of people ask, why is it called Spinner? I'll show you why. So when we shipped
our first SDK, this is what it looked like. So I ran this on my
Mac, and it does spin. [LAUGHTER] So we're not entirely
crazy or stupid, we just changed our mind. Another example that I
really like is Button. Button, if you look at the
source code of button.java, it's extremely interesting. There's very few
lines of code in there because it extends TextView. Why? It's a good question. Because when you
need to display text, the problem is that TextView
can do a lot of things. For instance, you can make a
button selectable if you want, and I'm pretty sure you
can also make it editable. [LAUGHTER] All right and of
course, there are tons of other issues
but Adam who's going to talk more about those. ADAM POWELL: Yeah,
Romain told me that I had to take
the slide that started with fragments here. I'm not entirely sure why. So as you stop to factor
some of your UIs on Android, and you start to have to
make some decisions about how you're going to write
your UI components. And this is a question that
we've had a lot over the years. Do I write a fragment or do I
write a custom view or custom viewgroup? And the problem with this
is that the answer can be really different depending
on the kind of component that you're writing,
and each approach has some of its own challenges. For example, if you
see this line of code somewhere because someone
decided that they were going to write a custom view to
solve a particular problem, what exactly is
your first reaction? ROMAIN GUY: Awesome. JIM SPROCH: No, Romain. ADAM POWELL: No? OK, so custom views are kind of
daunting to write because you really have to get
so many things right, especially if you want to expose
a first class API surface. You've got properties that
need their getters and setters, pretty standard stuff. You've got XML
attributes, which means that you need to start adding
in more stuff in your attrs.xml, define styleables. Speaking of stylables, you need
to define actual default styles and probably edit some
default theme attributes to make sure that all those
styles get wired up properly. And that's before you even get
to the sheer volume of code that you need for dealing with
things like layout or touch events. So meanwhile, fragments give
you a whole lot of flexibility, plus all the lifecycle hooks for
hanging your application logic from, especially now that
you've got arch components to give you some of this
in a more pluggable way. But this kind of
makes them better suited for coarser
grained situations. Reusable hold article displays
with interactive subcomponents and not really so much
things like single buttons. But the problem with this is
that your apps aren't static. They really change
and grow over time. So there's a potentially
really high engineering cost to having to cross
over from one of these abstractions to another
as your use cases change. So you insulate yourself
from that change. You build other
architectures to keep your views, your fragments,
and activities as removed from these things as
possible, and maybe actually make it a little bit
easier to test along the way. No matter which of these
architectures, or some of them that aren't listed
that you might choose, they all take on the
same basic shape. You bind some of this
stuff in your activity, your views publish
events and receive state, and everything in between
is kind of a judgment call. So these architectures
really exist to help define and reason
about your apps' data flow and answer the three
main questions. So what is the source of truth? Who owns it? And more specifically,
who can change it? So once you answer some
of these questions, then you realize that
the Android UI toolkit doesn't really work this way. Views kind of own
their own state and make their own changes
whether you want them to or not. Most of these view
state changes at least have some optional
listeners that you can attach so you can respond
to some of those changes and do something about it. But really, the Spinner is
another great example here, as odd as it is. And it's got this
onSelectedItemChanged listener that tells you when the
user changed the value. You've picked a new
value from the dropdown. And it gives you this callback
after the items already changed. And for a lot of UIs,
this is kind of useless because then you have to
change it back after the fact. So if you've ever
created one of those UIs where you have a custom dot,
dot, dot, you pop up a dialog, you've let the user make
some custom selections, and then you have to
change the adapter, set the value to something new. It's kind of a song and dance
that's not a lot of fun. And you also have to
deal with this idea of these reentrant
listener calls once you do set something back. So your listener has to
handle both the cases where you meant to
change it on purpose or when the user did it. And that just is more and more
code that you can get wrong. But there's some advantages
to this kind of approach. I mean, like, I
would love to just be able to change my salary and
then tell Romain all about it after the fact. ROMAIN GUY: We'll
talk about it at work. ADAM POWELL: No, this is me
telling you after the fact. ROMAIN GUY: Yeah,
we'll talk about it. ADAM POWELL: OK. So speaking of events, when you
have a single source of truth, you don't have all
of these problems. Every piece of
state has one owner. And only the owner
can make changes to that particular state. So that means that
the owner is listening to events that are happening
somewhere else in the UI, and it makes
changes in response. How many of you have
seen some code like this? Hopefully, not too recently--
oh no, we've got a few hands. OK, so Android's UI APIs
were really kind of designed for this world-- handling events by implementing
several different listener interfaces in one class
where that state lives. By implementing these
interfaces where the state is, you can make sure
that you don't have to lace that state through
too many different places. And this is kind of gross,
but lambdas weren't even part of the language when
this was common practice. So we can take a
few liberties here. But if you let somebody who's
comfortable with Kotlin lose, they might go nuts
and play some golf. They might write
something more like this. And the advantages of
something like this is that it's event
driven and it's built on composing
behaviors rather than based on inheritance. You can kind of get
away with implementing a bunch of interfaces
on a central class like the slide we saw before
when you have a small team. If everybody fits
in the same room and you can just ask
each other questions, you know that you're
not going to make a mess in front of one another. But as app and
team sizes grew, it became much less
sustainable to do that. So by leaning into
Kotlin in particular, we can write much more powerful
and more composable APIs. But really, whether
you've decided to write a fragment
or a view, the changes that you have to
make really kind of look similar if you're trying
to write something that's really reusable, something
that exposes a first class API surface for other people to use. You'll have something that looks
either MyFragment or MyView, and it might be a
Kotlin or a Java file. You have an XML file
that holds your layout for that particular
file potentially, if it's something
complicated, if it's something more than just a simple
button that clicks. You've got some stuff in
your attrs.sml file that defines the stylable and
all the different attributes that it can take. And you also have to
make sure that you don't step on that
shared namespace that resources work in. Can't declare two attributes
for two completely different stylables if they
have a different type. That would be silly. And then finally, you have
to deal with everything around styling. So you're spreading
all of this code that really has to do with
the same component across many different files. And by breaking that
spatial locality, it makes it harder to regain
context or flow at a glance after you've been interrupted. So for example, if you
have pets or small children or an open plan
office, you might get interrupted pretty often. So really we're trying to
address all of these things at once. So first off, we
want to make sure that we unbundle from
the platform releases so that you can take
advantage of what we're working on immediately. We want to make sure that you
have fewer technology stack flow charts to traverse
when you're deciding how to build your component. We want to make sure that
we're not telling you, well, if you're doing something
that's bigger like this, start with a fragment,
otherwise a button. And if you're not sure, flip
a coin, see how it goes, maybe refactor later. We want to help you clarify
state ownership and event handling. This is really big
because this is one of those things that
ends up being almost an afterthought in some
applications if you are not thinking about it upfront. But it's really the flow of
data through your application that can be the most
complex interactions. And finally, we want help
you just write less code. So the simplest program
that you can write in Kotlin is really, really simple. So whenever you're
writing a GUI app though, they're not this kind
snapshot in time. It's not a fire and
forget construct where you run it once and
then the program terminates. You're done, it's done its job. So we kind of need to handle
this idea of an event loop and things that
change over time. And Kotlin really gives
us some huge advantages for working with
asynchronous code now. But can we go even further? Can we make it as simple as
this to write an Android app? JIM SPROCH: Well, maybe we can. What if instead of calling
print line, we called text? What if calling text
would admit a text widget to the UI hierarchy? This is the idea
behind Jetpack Compose. It's a declarative UI
toolkit built for Android. It's inspired by frameworks
like React, Litho, Vue.js, and Flutter, but it's
written completely in Kotlin and is fully compatible with the
existing Android view system. It aims to simplify the
way that you write code by making your Android UI
hierarchies more declarative. With Compose, your UI is
defined as a function, and this function transforms
data into view hierarchy. In this case, the
input is a simple piece of data, a person's name, and
the output is a UI hierarchy, is a text widget. When you need to
update the name, we can just re-invoke this
function with the new data, and the UI hierarchy will
contain a text widget with the new text. This radically simplifies
your application because you no longer
have to have separate code paths for inflating
the initial views and then updating
the views later. If we invoke the function
with an appropriate name, then we'll get text, hello
world, on the screen. When the data changes, we
can invoke the function again with the new data
and the new data appears with a new
greeting on the screen. Each time we invoke the function
with different data, the text on the screen is updated. But what exactly is Compose
and how does this work? Among other things, Compose
is a completely new set of widgets and
toolkit APIs built on top of composable functions. They're not views. They're not fragments. They're something smaller, more
modular, easier to work with, and easier to test in isolation. Compose is also a
Kotlin compiler plugin, which makes it possible to
define your own declarative functions using our
declarative API. The Compose compiler is
what powers the new widget set and makes it easy for you
to define your own composable widgets. Compose widgets are also fully
compatible with the existing view system. Whether you're working with
Kotlin or the Java programming language, we
provide ways for you to use the new widget set in
your existing view hierarchy, and you can use existing views
in the Compose hierarchy. You can mix and match, you
can adopt it incrementally and you can adopt it at your own
pace, just like Kotlin itself. It's also very early stage. We're still in
active development, and we're moving to ALSP
so that you guys can get an early preview
and so that you can be a part of its development too. So we can collect
feedback from you, but it's not ready for
production use yet. If you did want to use
Compose in an application, it might look
something like this. You can call composable
functions directly from the content
of your activity. All composable
functions are annotated with an @Composable annotation. This tells the Composer
compiler that your function should be treated as a widget. And it allows us to jump in and
perform the necessary rewrites at the right time. We've been working
with the JetBrains team to make all of this possible and
it's still very experimental. But basically, we intercept
calls to composable functions, and that's what allows us to
provide the declarative API. Ultimately these
functions are fully featured Kotlin functions,
which allows us to do some really interesting things. For example, if we wanted
to greet the user 10 times, we could use a simple for
loop to do exactly that. If we want to iterate
over a list of names, we can greet a whole bunch
of people all at once. We can also have logic to show
something completely different when the list is empty. But the most important thing
about composable widgets is that they're composable. Compose makes it trivial to
define your own composable functions and reuse them
throughout your application. Your UI hierarchy
is built by having composable functions call
other compatible functions. ADAM POWELL: So our story
widget here uses several stock elements that we're going
to provide out of the box to build up a pretty
straightforward layout, but you might see
another issue here if you're not suspending
disbelief quite enough looking at our slide wear here. So as we know, if
that list right there is really, really
big, then this would create a whole lot of
these story widgets that we're not going to see. As Android developers, we've
been trained over the years to avoid this. In fact, I think
about 10 years ago, Romain and I gave our
first talk together where we were talking
about list view. So this today would be the
job of a recycler view, kind of our list view two widget
that you're all probably familiar with. But that means that we have
to factor out an adapter to handle the recycler view
itself requesting and rebinding those views. So as you probably
know, there's really no good way to fit a recycler
view adapter onto a slide. It's a lot of code
even in the best case, and I'm using data binding
to cheat a bit here. And several things just
aren't shown at all, like the diff callback for
helping to handle animations on data set changes. And we certainly
don't have anything about event handling in
here, but you kind of get the basic idea. So let's come back to this loop
in the Jetpack Compose version and see what else we can do. Well, this is Kotlin after all. We can define a generic
composable function that looks like a for each loop
by using a trailing lambda argument and have it emit each
story widget as it's asked for. So what we did here is we
just defined our recycler view adapter in one line of code. So the implementation
of the scrolling list itself can be just as
smart as a recycler view. The body that we pass to
a composable function call can itself be a
computable function lambda so that scrolling list
can choose to call it multiple times or not at all. Much more powerful than
just passing parameters through an include tag since
it means the scrolling list doesn't need to know anything
about what we're going to put in there when we use it. You can put entirely
different structures in here just depending on
code that you're running and defining. So we can do some other
kind of neat things too. We can offer some
really powerful hooks into our runtime for lifecycle
and subscription management that can work
together seamlessly with things like live data, RX
Java or the new Kotlin flows. So up until now, we can do
most of what we've shown here just with a clever DSL. But here, observe will return a
new value each time it changes and cause the news feed
function around it to run again. So that'll feed new data
to our scrolling list much like a recycler view
adapter data swap will. So observe as shown here is
just defined in library code. There's nothing special here. There's nothing that
we've implemented that's based on internal API. You can write your own, too,
if you have your own favorite observable or callback types. So what else does
this imply about how the scrolling list must work? So let's go ahead and
put it into something a little bit more sophisticated. So we saw with
observing a live data that we can trigger a
recomposition when something changes. So let's use an async
loading function that can give us a
placeholder immediately but the result of running
some sort of lazy loaded block once it's actually completed. So all of these sorts of
complex image loading scenarios that we're all
familiar with suddenly become something very simple
that we can use anywhere. And also like we saw
with the scrolling list, we can weave data across
layers of the hierarchy, and they don't have to be
direct tree children either. It doesn't matter
what kind of chrome or how many layers of hierarchy
that card or column might add in between in
order to do their work. So we can go further than
we can with something like data binding, and we can write
a whole lot less wire up code. We can just use the lexical
scope of that image or story that we already have, and we
can refer to the story headline several layers deep. This is really
part of what helps make composable
functions composable, how they're kind of
getting their name. We can leave this as is. I mean, it's a lot
of code for a slide, but it's not a lot to
glance at an editor. Or we can just use our
normal tools that we've got and highlight extract
method on any part of it and go ahead and generalize it. This isn't anything magic. It's just the same
tools and techniques that we're already used to
doing when we're writing code. So we've talked about how we
can observe changes in live data and we've seen how we can show
place holders while we request a data load from somewhere else. But what if we get an update to
one of our story data objects? So our story data that we
use as a parameter in there is really most
naturally represented as a Kotlin data class. There's really nothing
too special about it. But we'd like to be able to
update our UI if anything about it changes. What if this was mutable? So if it were a fragment, we
might use an arch components view model with some
live data as properties and use the same technique
that we saw earlier. But this is going to get really
repetitive really quickly, especially with all that
extra subscription management, we're kind of deciding
where all of this goes. Really we just wanted to write
that code from the last slide, and you can. So Composer's compiler plug-in
lets you mark a data class as a model which make the
properties observable when it's used in a compose function. Now, if our story
changes, the story widget that read its fields
will know to recompose. The Compose runtime
observes the changes and will cause the story widget
function to rerun and update our UI with the new data. JIM SPROCH: When you're
generalizing your code like that, it's really
important to break it up into multiple
compatible functions and understand how the data
flows through your application. The application passes data-- in this case, a
list of stories-- into the news feed. The news feed is then
going to iterate over this list of stories,
extracting each of the pieces of story
data from the list and passing one story
data to each story widget. The story widget then
reads the title, the image, and the content
from the story data. The important thing here is that
the parent is always in control of the data for the child. If the data needs to be shared
between multiple widgets, the data should be hoisted
up to a common ancestor and passed down to each of
the widgets that needs it. Children should not be
reading from global variables or global data stores. They should be a
side effect free and should not do
anything except what the parent tells them to do. As your application
scales, this architecture makes your code much
easier to reason about. But what happens if the
application wants to know when a user clicks on a story? One interesting pattern
is to pass lambdas down because code is data
which allows us to have events propagate upwards. It looks something like this. In this case, the news feed
takes an unselected lambda, which is going to take
in some story data, which is the current story. It will then, when the news
feed invokes the story widget, it's going to take
this lambda and create a new lambda of its own and
pass that lambda to the story widget. The story widget then
passes that lambda to a clickable widget, which
will respond to the click. When a user clicks on the story,
clicks on the clickable widget, the unclick will fire
which will cause the event to propagate back up
to the lambda that was created and passed in. This lambda will
call unselected, which is the lambda that
was passed to the news feed by the application. So the parent is
always in control. The data-- in this case, the
lambda-- is being passed down, and then the event
bubbles back up. If we look at this
in diagram form, we see that the application
starts with some data, passes it down to the news
feed, which passes it down to each of the story
widgets, which in turn pass the information down to the
composable building blocks that build up each widget. Then when a user clicks
on a piece of the widget, that triggers an event which
calls the lambdas going back up the hierarchy,
back up to the screen. Ultimately the application can
then update the applications data model and the
new data will be passed back down all
the way down until it's displayed onto the screen. Notice that the data was
always passed top down and data always comes in as parameters. The widget didn't have to
reference the activity. It didn't do anything
except code that was controlled by the parent. We'll come back to this
with the Spinner example. ADAM POWELL: That's right. So the Spinner has a problem
that the code that we just saw doesn't in that the
Spinner makes you deal with multiple sources of truth. So our app has a state,
the Spinner has a state, and the Spinner doesn't
ask us before letting the user change its state. That's the basic
shape of the problem. And there's two ways to solve
this when you have this, like one of these sides
must win if you want to have a single source of truth. So we could design some
of these components such that the Spinner itself
is the source of truth. And you could see how you
would put that together even with the API that we just saw. And it basically
looks like having to add a whole bunch
of validation APIs. It means that every
user interaction widget needs these fully
formed APIs for anything that you can change to sort of
filter through what you might do in order to validate that
what the user's inputting is actually correct. And it scales kind of poorly if
you're writing those components yourself. You want to have a case where
it's turtles all the way down. You want to make sure that it's
as easy to write a component as it is to consume a
component, so you kind of don't want everybody
to have to deal with writing all their
APIs twice, writing these sort of validation
filters for every property that you can set. So the other option
that we think is better that we're
certainly pitching here is to let the app
own its own data and have the apps backing data
model-- the source of truth that you're using
that's part of your data repository be the
source of truth instead. So this has a whole lot
of other advantages. JIM SPROCH: Let's take a
look at a concrete example. Here we have a food
preferences widget that we define as a
composable function. It takes in some
user preference data, and ultimately it
will emit a Spinner which has some set of options
that are available to the user. Notice that the
Spinner's data is set by the selected parameter. So the favorite food of
the user is passed directly to the Spinner. The parent widget, in this
case food preferences, is in control of what
the spinner is showing. And when the spinner
has a user indicate that they want to make
a change, the user can then execute
the unchange code which will result in
the data being updated. The Spinner doesn't know
which data is being updated or what to do, it
just calls on change. When the data does get
updated, this Compose function will get recoomposed and the
new selected value will be set. We can apply the same
approach to text. So in this case, we
have an editable text for the user's name, and all
that the unchanged handler does is set the name. This looks pretty
generic and you might wonder why the
editable text couldn't just set the text itself. But maintaining this
one-way dataflow allows us to do some
really interesting things. For example, if we have a phone
number preferences, then when a new potential value comes
in, we can do filtering. We can remove any
invalid characters, and then we can format the phone
number so that it looks nice when the user is typing it in. And then we set the data model
to the new formatted phone number. Again, this will
trigger a recomposition which will cause the editable
text to show the new value. ADAM POWELL: All right, so
let's go ahead and go back to some of our goals
from the beginning that we listed through. Did we actually meet
some of those goals? And I kind of think we did. So in order to meet
our unbundling goals, we're open sourcing a new UI API
and component set for Jetpack We've offered
computable functions that scale across these
layers of abstraction. A composable function
can do the same sorts of jobs as a fragment or
as a custom view group or even as something as
small as a single button. We've kind of established this
guideline of one-way observable dataflow as a
first class concept and we're building the system
to make it easy to sort of stay on this happy path. And finally, you have
less code and you can keep it in one place. You don't have to split your
focus across multiple files just to write a
piece of your UI. JIM SPROCH: OK, that
was a lot of information in one short talk,
and we're only just scratching the surface. Some of you look
excited and others might have a lot of questions. For example, what about
view compatibility? Will this work with
the existing views, and how do I integrate
this into my application? So one of the things
that you can do is have a annotation
that indicates that this code should be usable
from within a Android view. And when you do that, it
will generate a view class. This class is just
a standard view like any other from your
code possible function. And you can use it from
XML, as shown below, or you can call
the find view by ID and get a reference to the
node and pass data directly to the view. There's other things that
might be interesting to you. For example, we've been
giving a lot of thought to how Layout Editor and
Preview Tool would work. We've been giving
a lot of thought to animation, styles, themes,
and constraint layout. All of these things are
really important to get right before we go with
a stable release. And so we're going to make
sure that we have support for everything that
you would need to build beautiful applications. And we're still thinking
about a lot of other things. We're continuing
this exploration. We have ideas around
multi-threaded layout, around asynchronous
parallel execution of composable functions, around
memoization, and much more. All of these things
become feasible because of the declarative
programming model, and we encourage you to start
exploring these ideas with us. We'd love to hear your ideas. ROMAIN GUY: All right just
want to make one clarification. You saw an example
of Jetpack Composer that was using a Spinner. We don't have a spinner yet,
and it be called Spinner. [APPLAUSE] ADAM POWELL: How
are people going to understand what to use? ROMAIN GUY: But if
you really want one, you can build one,
that's the whole point. Yeah, so I just want
to reiterate that here, one of the key
elements is-- well, two key elements that I really
love-- first, composition. If you go look at the
source code in AOSP, you can look at how some of
the widgets were implemented and you'll see they
are that simple. You start to write
some code, but you should look at the
checkbox, for instance, it's just a few lines of code,
and everything it uses is available to you
to create your own. The one-way data flow model
is also really important because a lot of the bugs and
a lot of the [INAUDIBLE] plate in your code comes from
trying to keep two data models in sync at all time. And I mess that up all the time,
and I'm sure you do as well. So we just got rid of
the problem altogether. So Jetpack Composer is
not ready to ship yet. We've established that. Please go take a look
at the source code, if you go to
d.android.com/jetpackcompose, we have explanations. We have a readme that
explain how the source tree's architectured. You can run a special version
of Android Studio that will let you play with it, compile it. We have a few demos
that are available. You can look at the
entire source tree. But in the meantime,
you can get ready before this becomes available. So one thing you can do
is just play with it, but you can also try to
structure your application to try to fill this
one-way data flow model. So one thing you
can do already is try to eliminate
all the [INAUDIBLE] you have because every time
you do this, it's almost a [INAUDIBLE]. Especially if you
called get on the view, that means you're
trying to synchronize the data model of the
view with the data all of your application. And this is a potential
source of bugs. And you won't be able to do
this anymore in Jetpack Compose because your functions,
your composables, just emit a new tree. At no point can you get a handle
on the actual, not physical, but the actual new view object
that will be put on screen. So you need to get rid of this
habit like basically right now. Also you can start looking
at architecture components. How many of you are
using the arch components in one form or another? All right, you are all awesome. Everybody who didn't raise their
hands, run back to your desk and go fix that please. So you have several things
you can use like ViewModel and LiveData, but there are
also other solutions out there, so please try to use those. And now we have a
few minutes left, so here's the URL
that you can check out if you want to know more, if
you want to find a source tree. [MUSIC PLAYING]
I was afraid it would just be another Anko layouts but not at all, it's a shift paradigm to UI systems like React and Flutter. I am super HYPED!
As I am playing around with React and Kotlin/JS in a Kotlin Multiplatform project I learned this pattern recently and it does feel right.
You don't reference views but let's say an editText changes so onChange you set that text into a State object which you then use when you need that value. It makes somewhat the UI code clearer.
I wanna see this in the new Motion Layout editor 😍
Can someone tell me why they are telling us about making these functions as pure as we can and then they add
@Model
annotation that uses mutable properties? We already haveLiveData
andDelegates.observable()
in Kotlin.Why not simply call the component with the new data when it's ready? Why should we bother setting our mostly immutable (in the context of MVI) models to a mutable buffer somewhere in the UI?
They said most of the right things to alleviate my fears. I was skeptical at first, but this looks like it might be pretty amazing.
I've used react before and thought it would lead to some bad application design. Particularly because UI components are suppose to directly control the application state. They explicitly call out that behavior in this video. Yay!
Two thing I still don't fully understand how it works, is how do you replace inheritance. Like how do you create common shareable behaviors for views.
And how do you reference the views to manipulate them later?
It's that
@GenerateView
annotation that makes this really interesting.I like the new direction taken by Android SDK guys. Here is some feedback about the presentation.
Overall, I like the new API more than the current.
Some guy here said, that he works on Compose at Google, are you here? Can you say anything about performance? How is it so far, compared to default View system?
YESSSS. REDUCE ALL THE THINGS
Yay for
FlutterAndroid Compose!I'm not sure if I understood correctly, but didn't they recommend to make UI model classes mutable? Does it mean they are still not ready to let the UI hierarchy be touched by multiple threads?