[MUSIC PLAYING] NICK BUTCHER: Hi. I'm Nick, and today
I'm going to talk to you about how Jetpack
Compose makes Android UI easier. But I'm not just going to tell
you, I'm going to show you. Jetpack Compose is our new
unbundled, declarative UI toolkit now in alpha. It's made for the demands of
rich, beautiful modern apps. But to me the best
thing about it is that it simplifies and
accelerates UI development. It makes Android UI easier. Not just easier, but it
makes the easy things easy, and it makes the
hard things possible. But don't just take
my word for it. I'd like to show you
how Compose does this. I'll do so by introducing
you to some brand new samples that the team
have created, which we're open sourcing today on GitHub. Joining our existing
JetNews sample, we're adding four new
samples to the Jet family. Each sample demonstrates
different use cases and APIs. Jetchat and Jetsurvey
are all simpler apps, a great starting point to
begin learning Compose. Jetcaster and Jetsnack
demonstrate more complex UI with more theming and animation. We're also releasing
free samples showing how Compose's
implementation of material design can be customized
for your brand. All of these samples are
available right now on GitHub. Today I'd like to take you
through some specific examples from these apps to show
you how we built them and how Compose simplified
their development. So let's jump right in. First up, theming. Compose ships in
their implementation of the material design system. To our sample app,
in particular, leverages Material's theming
system to highly customize its look and feel. For example, it uses completely
different color schemes in different
sections of the app, with alternate versions
of each color scheme and [? dot ?] theme. Material theming
is a systematic way to customize material designed
to reflect your product's brand. The material theme
comprises color, typography, and shape attributes. Customizing these
attributes updates the components
throughout your app. In Compose, this is modeled by
the material theme Composable. It accepts parameters describing
your desired color, typography, and shape. Let's take a look at
each of these parameters. The Colors class models
Material's color system. There are also Builder functions
implementing the default or baseline color palette. So you can build on top
of this just specifying the colors you want to change. So to implement
OWL's color scheme, we create a series of
these color's objects using the Builder functions. Similarly, textiles and
model by their Typography object, whose default parameters
implement the baseline material theme, which uses Roboto. So you can emit any that
you don't want to customize. Each typographic style is
modeled by a text style that you customize its font,
size, and weight, et cetera. Material shape theming
specifies three categories of shapes for small, medium,
and large sized components such as buttons, cards,
or shapes, respectively. You can create
rounded shapes where they're given corner radius
or percentage or cut corners. For both styles, you can specify
individual corner treatments or they all should use the same. So now that we understand
the parameters, let's set up our MaterialTheme. We recommend doing this
in a Composable function to centralize these
changes and make it easy to use across your app. Here, were modeling
OWL's YellowTheme, configuring the colors,
typography, and shapes to use. So to apply our desired
styling to this screen, we simply wrap it in our
YellowTheme Composable. Themes can also be nested. This detail screen, for
example, uses a PinkTheme for most of its content, but
includes a related section using a blue color scheme. We can nest the BlueTheme
around the section to change the theme colors. Theming has always been a
complex topic on Android. By embracing Material and
offering an implementation of it rather than the generic
and loosely type system, it makes it simpler
to understand and easier to work with. For example, we gain type
safety when working with themes. Here we are retreating and
using the color and type styles we set up and can
even autocomplete them helping you to
discover what is available and making it easy to access
colors through the theme. It's also simple to derive from
theme colors by copying them. This avoids hard
coding colors, which makes it impossible to
support multiple themes like dark theme. Because the
components themselves understand the
MaterialTheme, they can also offer smart defaults. For example, when you
set a background color here using a Surface
component, then elements within it such as text and
icons automatically default to the correct color. Here we are using a
primary colored background so the text color and icon tints
default to the onPrimary color. It's simple to see how Material
components use theming. Here Compose's FAB has a
background color parameter, which defaults to
Material color secondary. No more hunting through
XML files to work out what color a component uses. Simply look at the
parameter's default value. As we saw before, Compose models
colors as a collection of color values. We can support dark themes
by adding a single parameter to our theme. Here, I'm defaulting
it to a function, which queries the device's
darkTheme setting, but you can override
it in places where you want a particular theme. Then we simply switch color
sets based on this value. Notice how there is nothing
specific to light or dark themes here. You can easily support multiple
different color schemes using the same techniques. So not only does Compose
make theming easier, it makes some things possible. For example, Compose
enables dynamic themes. The Jetcaster sample
is a podcast app, which uses the artwork for
the currently selected podcast to theme the UI. It uses the palette library
to extract the dominant color from the artwork, then copies
the existing color palette, setting the primary color
even animating this change. It then uses these updated
colors to theme the UI. This was really hard to achieve
with the XML theming system. Themes being handled entirely
within your application code also enables the
possibility to test them. For example, the
Jetchat sample tests what happens when the theme
changes between light and dark. In our test, we
set up a state flow of whether the test
should use a dark theme. In setup, we observe this
flow, and pass it to our theme. Any changes to the flow
will now re-compose the UI. Now, in our test, we
can manipulate our UI into our desired initial
state and a set against it. Then we update the flow
to switch to darkTheme. This causes the UI to update. We can then assert, again,
checking that the theme change happened as expected. We can even capture
screenshots or compare these against golden images. In the past, creating
your own design system has been challenging. While we recommend
you use Material Design and compose shapes
and implementation of it, there's nothing
special about it. It's built on public APIs,
and it's entirely possible to build your own design
system to extend or replace it. The Jetsnack sample defines
its own design system with a custom color
palette, including heavy use of gradients. This comes together in
a custom color system. Here's a screen from the
app using this color system. Notice how components are
customized to use these colors like this button with
a gradient background. To implement this, we can
model the color system like Material did with our own
design system's semantic names and represent the gradients
as lists of colors. Now, we can't just pass this to
the MaterialTheme Composable, as it doesn't
understand this type. Instead, we can do
exactly what MaterialTheme itself does under the hood to
implement our own color system. There are three
steps to doing this. First, define an ambient
to hold your colors. But wait, what's an ambient? Well, usually in
Compose, data flows down through the UI
hierarchy as parameters to each composable function. This can, however, be cumbersome
for really widely used things like colors or type styles. Instead, Compose
offers ambient, which allow you to create named
objects to look up values from like a service locator. This is what MaterialTheme
uses under the hood to store the colors, types,
and shapes in ambient, allowing you to retrieve them later. Ambient is a scope
to a hierarchy, so you can provide
different values at different levels of the
tree like we saw before with the colored subsection. So to implement our
own color theme, we can create an ambient. This associates our custom
type, JetsnackColorPalette, with the ambient
name, JetsnackColors, and it provides
the default value. Then in our theme, we use
the provider's Composable to set a value for this ambient. In Jetsnack, we still use
material, shape, and type theming, so we wrap
a MaterialTheme, and omit the color parameter. Here, I'm using an object
with a colors property to mirror how MaterialTheme
exposes values, but the key part is using the
ambient's current property. Consuming code can now access
our custom colors like this. Now that we have the
color system set up, we can customize
components to use them. If we look at Composer's
Button component, it accepts a single
color parameter and is put together by combining
three other composables-- a surface, a textile
provider, and a row. The surface implements
the color and shape, the text provide a
defaults children to use in the Button
textile, and the Row lays out its children. Now, I've omitted some
parameters here for clarity, but the entire component is
less than 30 lines of code long. This design is what enables
you to easily customize components to create our own. We can take the platform's
implementation and modify it. Instead of a single color, we'll
accept a list for a gradient and default it to a value
from our color system. We'll keep using
Material surface, but skip its solid color
by setting Transparent, and instead, we'll
draw a gradient. And that's it. We've created our
own Button component that we can use now
instead of Materials. Rather than a single
monolithic, library Compose has built-in layers. Each has its defined
responsibility and builds upon those below it. So you can add your own design
system on top of material or replace it altogether. The same principles
apply within each layer. As we saw, Material's button
is built from other components from the Material and
Foundation layers. Each component
does a single job, and you assemble them to
build up functionality. So to create your
own button, you can either build
on top of Material and customize its behavior or
replace Material entirely using lower-level building blocks. The next area where we found
that Compose made things easier was layouts. The samples we built
varied in complexity, from a simple login page to
richer forms or custom layouts like the staggered grid. Compose offers a variety
of layouts to build these. Columns lay things
out vertically, rows horizontally, or stacks
enable overlapping elements. Compose even supports my
beloved ConstraintLayout for more complex scenarios,
offering a really powerful DSL. While we found
these layouts worked great for building
the sample app, it was other aspects of
Compose's layout system that really shone and
made things easier. Firstly, Compose offers
a modifier system enabling you to configure
and customize your components or even add behaviors to them. Let me show you what I mean. Let's build a simple list
item from the Crane sample. This image and two
lines of text can be built using a row to
arrange things horizontally, first the image, then a
column holding the two texts. If we deploy this, we'd
see something like this. The image is too
large, and the texts are shoved over to the side. This is where modifiers come
in to configure components. All UI composables accept
a modifier parameter allowing you to customize them. Modifiers are defined as
extensions on their capital M Modifier object, allowing you
to discover and also complete them. Here, we're using the
preferred size modifier to configure the
image composable. So far this is pretty similar
to a view LayoutParams, but it gets way more powerful. We can add a padding
modifier to the column to move the text
and image apart. Now, modifiers support
a fluent syntax, so we can chain them together
to compose their functionality. For example, we can
add a gravity modifier to vertically center the
column within the row. Compose utilizes scopes
to provide type safety. Rows, they lay children
out horizontally and only use gravity for
the vertical positioning. Row therefore, defines a
gravity modifier in its scope, which only accepts
vertical gravities. Trying to use an invalid
horizontal gravity won't compile. No more desperately trying
different LayoutParams to see what works. Modifiers aren't only
restricted to layout. They can also affect drawing,
interaction, and more. Here, we are applying
a clip modifier around the corners of the image. By convention, a
modifier is always the first optional
parameter of a composable, so you can pass one without
having to name the argument. Here we're adding some
paddings to the entire row to move the content inward. We can add the background
color to the row by appending a
background modifier, but this produces this perhaps
surprising result. The reason for this is that the order
of modifiers is significant. So if you want the background
color to include the padding, we can swap their order. Now, this took me a little
while to get used to, but now I think it's a
really powerful design. This puts you in
control of exactly how different behaviors interact. It's not down to some
internal implementation detail of the component. It's under your control,
and it's deterministic. So if we want some outer
space around the whole row, we can add another
padding modifier. Yes, you can repeat them. Rather than having to learn
rules like the box model, which has the margins are on
the outside of an element and paddings within
them, you are in control. If you want to make this
element interactive, we can add a clickable modifier. If we add this before
the outer padding, then the entire
element is tappable, and the resulting ripple
will include this space. Or we can do that afterword to
confine it to the smaller area. You should read modifiers like
an ordered chain, each of which provides inputs for the
next element in a chain and have no knowledge of
the elements preceding them. This example reads as add
some space around my contents, then make it clickable, then
draw a background color, then add some more space. These were fairly
straightforward modifiers. But hopefully, you can
see how we can easily combine them together to
build up our desired behavior. There are many modifiers
available for adding all kinds of behavior, such
as making element tolerable or even adding high-level
behaviors like making something scrollable or even zoomable. We use modifiers extensively
for our sample apps to achieve their designs. For example, Jetsnack's
search category has combined multiple modifiers
to layout and draw this item. You can even create your own
modifiers such as this custom horizontal gradient modifier. This element also shows
another powerful aspect of Composer's layout
system, how easy it is to create custom layouts. The design of this item
calls for the text and image to be divided up as a percentage
of the available space. The image then peek in
from the edge of the card with a minimum diameter. Now, you might be
able to have built this with the standard
layouts, but Compose made it straightforward to
implement this exact design. The Layout composable
is the building block for custom layouts. It's what rows and
columns and cells use, but you can and
should use it directly to achieve your
own custom layout. It accepts children
to be laid out and any modifiers
to apply for them. You implement a measure block
to create your custom behavior. In this, you have three things
to do, measure each item, call the layout method,
and place each item. Let's walk through these. Find your custom
layout composable and pass the children and any
modifiers onto the layout. Then implement a measure
block as a training lander. We're given inputs of a
list of measurable items and some incoming constraints. The constraints
object is passed down from your parent telling you
the minimum or maximum size you can be. Each measurable object has
one main method, measure, where you measure each item
with a set of constraints. So step one, measuring. In a simple
implementation, we'll measure each item with
the constraints that were passed into us,
but you can and should alter these constraints to
implement the layout you want. Measuring an item
returns a placeable. This contains the
width and height the item would like to be
and offers a place method. Now that we've measured, we
need to call the layout method. This is where we report how
large our Composable should be. You can calculate this based
on the incoming constraints and the layout you're
trying to achieve. For example, a column
might sum the heights of each element it contains. The layout method
accepts a placement block where we need to place
each item on the screen. Here, we call place for each
placeable that we've measured, specifying it's x
and y-coordinates. Let's use this technique to
build the SearchCategory item. It's a custom composable
wrapping a call to the layout composable. Here, we specify the modified
chain we saw before to size the item, render the
background in shadow, and clip it to a
rounded rectangle. We directly specify
that this item should contain a text and an image. Our design calls for the
text to occupy a percentage of the overall width. In our measure block,
we calculate this as a fraction of the incoming
constraint's maximum width. Then we call measure
on the text item, passing constraints
with this exact width that we want it to be. A design calls for
the image to have a minimum size of 140 depth,
so we measure exactly the size. We can then call
the layout method using the incoming
constraints maximum width and height to fill
the available space. In the placement block, we
place the text on the left and center it vertically. Then we place the image
to the right of the text by specifying the text
width as the x-coordinate and also center it vertically. The image may overflow
the bounds of this item, but the clip modifier we
specified will mask it. Like this, we've achieved
the exact design we wanted. Compose made it as easy as
implementing a function. Compose's layout system also
enables new possibilities. It disallows you from
measuring an item twice. If you try and do so, it
will throw an exception. This gives you
performance guarantees as you can't get into
excessively costly measure cycles. It means that you can
measure and layout in places that might have
been prohibitive reviews. For example, this detail
screen scales and translates the main image as you
scroll the content. This is entirely achieved
for a custom layout. We calculate a
collapse fraction based upon how far you've
scrolled, then we use the lerp
function to calculate the size for this fraction. We measure the
image at this size and calculate the
appropriate coordinates to move it from the
center to the top right over the course of the
gesture and place it there. You can even directly
animate an element size. In this example,
Jetsnack's main navigation called for the selected item
to be wider than the others. This was achieved using
a custom layout that holds a fraction between 0 and
1 for how selected each item is. When an item is
selected or deselected, we animate this value and use
each item selection function to calculate its
width, measuring it with this exact width. Achieving this kind of
effect with a view system was prohibitively expensive and
likely to be unperformanced. Composer's design
makes it not only easy to implement the exact
layout that you want, but even to animate
changes to it. Speaking of animation,
this is another area that we found Compose greatly
simplified OWL, for example, presents a number of topics
which animate between selected states. To round the corner
of a selected item, we can conditionally
set a different corner radius of its shape. To animate this
change, we simply wrap this if with the
animate composable function. Whenever the selectors
state changes, it will animate
between these two dp values and any
composables that read the radius will be updated. That's it. This works great if we only want
to animate a single property, but our desired animation
is slightly more complex. We went round the
corner, but also fade in a pink overlay
and a checkmark. To do this, we can switch to
a different API, Transitions. This allows you to animate
sets of properties together. First, we define key
for each property that we want to animate,
and the type of value will be animating. Here, we'll use dp properties
for the corner radius and float properties
for the others. Next, we define the
different states we support here using an enum. We only have two
states selected or not, but you can define
as many as you need. We then create a
transition definition specifying the values
our properties should have for a given state. So here, we're
specifying the values when the item is selected. Now, in my topical item, I
can switch to the transition composable function, passing
in the definition and the state it should be in. Whenever the selection
state changes, this will recompose,
kicking off an animation. It returns an object
holding the animating values from which we can retrieve each
property using getter syntax. Compose animation uses
a spring-based system by default, which provides
smooth interruptions out of the box. You can also specify tweens
using durations and easings. To help you build
great animations, I want to give you a
sneak peak of some tooling that we're working on. The Animation Inspector
lets you preview animations, helping you to get
them just right. You can select different
states to check the animations between them. You can slow down, playback,
or scrub through the timeline to dial in those values. This is coming soon to
an Android Studio Canary release near you. So not only does Compose make
our animation code much easier to write and reason
about, it also enables new possibilities
like testing. The Rally sample uses a
prominent animated chart to display your finances. This is a key moment in the app,
so we want to add Test for it. In Test, you have access
to a test animation clock giving you the ability
to control time. You can pause and manually
advance the clock, which drives all animations. To test our animation,
we can start our test with a paused clock. We add our animated
elements to the UI, and then manually advance
the clock to some point during the animation. Now, we can assert about the
state of UI at this point, or here, we're
actually capturing a screenshot of the
element to compare against the golden image. In the sample, we're running
a simple comparison on device, but you could easily put this
into your Test infrastructure to run on CI. Hopefully, I've
shown how Compose makes Android UI easier, and
it enables new possibilities for building amazing apps. In addition to the
samples you saw today, we published five code
labs and more documentation to help you learn
all about Compose. Compose is now in alpha
launching 1.0 next year. Now is the best time to try
it and give us feedback. The major concepts
are established, but API is still evolving. Your feedback and feature
requests can help shape it. Check out the samples,
code labs, and docs, and give us feedback
on the issue tracker or talk to us on Slack. Thanks for watching.