(soft music)
- Jetpack Compose is Android's new, modern,
declarative UI toolkit.
If you're not sure what Compose
or a declarative UI toolkit is,
then you're in the right place.
I'll talk you through it and why I think it's great.
Compose is built on Kotlin using its full power
and express enough to build intuitive APIs.
Jetpack Compose is unbundled, like all Jetpack libraries
so you can update at your own pace
and have consistent behavior across API versions.
And lastly, we know that you have existing apps
which you can't just rewrite with Compose,
although after you've used it, you might want to.
Compose is designed to interoperate with Android Views
so you can gradually migrate.
Today, Jetpack Compose is in beta.
That means it's feature complete for 1.0,
and we'll be focusing on polishing and stabilizing the APIs.
Now is a great time to learn Compose
and get ready to adopt it.
But to me, the most exciting thing about Jetpack Compose
is that it makes building Android UI faster and easier.
Let's examine how it does this.
Compose is a declarative UI toolkit.
Let's break those two concepts apart
and look at each in turn.
First, the declarative part.
Today, apps aren't static data: they update; they're alive.
Traditionally with Android Views,
you would declare your UI in XML,
but then, to update your UI, if any data changed,
you need to mutate it.
You do this by looking up views
and setting properties on them.
So whenever your application state changed, for example,
once a database or network call loads
or after user interaction,
you would have to update your UI with this new info,
to keep everything in sync.
This can get complex
as each view holds some state that you need
to ensure you update.
This overhead of synchronizing state
between your model and your UI is a huge source of bugs.
It's your responsibility as a developer to ensure
that everything is updated.
As your app and UI gets more complex,
it's easy to get this wrong.
Declarative UIs, like Compose, take a different approach.
They transform state into UI.
The UI is immutable.
There's no way to update it after it's been produced.
So if your app state changes, we re-execute
to transform this new state into a new representation.
This avoids the synchronization issues entirely.
You regenerate your entire UI.
Compose is actually smart enough to skip any work
for elements that haven't changed to make it more efficient,
but conceptually, it regenerates the entire UI
for a given state.
Your code describes what the UI should look like
for a given state, not how to produce it.
So what does this look like?
How do you transform state into UI?
Here's a simple component that displays a list of strings.
Notice that in Compose, a UI component is simply a function,
with the composable annotation.
This makes it cheap and easy to create UI components,
encouraging you to break your UI down
into a library of reusable elements.
The function doesn't return anything,
but instead emits UI.
Here, we're using the column and text composable
from the library, which arrange things vertically
and display simple text labels.
Note that we can use the full Kotlin syntax
and control flow to produce our UI.
Here, I'm iterating over the messages,
but if you want to conditionally show an element,
here showing a label when there are no messages,
then it's as simple as using an if statement.
Composables can and should accept parameters.
That's what I mean by transforming data into UI.
Composables take data as function parameters and emit UI.
In this way, the UI can't get out of sync.
For example forgetting to remove the no messages empty text.
If your state changes,
here, if the list of messages changes,
then a new UI is generated by executing this function again.
We call this recomposing.
So how might the list of messages change?
Walking up the call stack,
our viewModel exposes a LiveData of messages.
We can observe this,
and now any composable that reads the messages field,
will be recomposed whenever there is new data.
You don't need to set up observers yourself.
The Compose compiler tracks which composables read state
and automatically re-execute them when that state changes.
It's smart enough to only re-execute composables
who inputs have changed
and skip those that haven't, making it very efficient.
Each composable is immutable.
You can't hold a reference to it
and query it later or update its contents.
You need to pass any and all information
to a composable as parameters when you emit it.
But that doesn't mean that a component can't change.
Let's dig into this by adding a checkbox
to select all messages.
Initially, our checkbox is unchecked.
Unlike in the view world,
clicking this checkbox would not visually toggle it.
To do that, we need to update some state
and re-emit the checkbox.
Let's introduce some local state
of whether it should be checked.
Checkbox offers an onCheckChange event when it's clicked.
We can update our local state in this callback.
Doing so will cause the checkbox
to be re-emitted as it reads this state.
It's important to understand
that if we don't change any state in this callback,
the checkbox won't update visually.
At first, it might seem annoying that you need
to write code to make a checkbox toggle when tapped,
but this is the key concept in declarative UI.
Elements are completely controlled
by the parameters you pass to them.
This is what produces a single source of truth,
meaning that there is no other state to keep in sync.
It's entirely up to you to decide how to react
to the user pressing the checkbox.
You might want to run some validation
and only update the display if it's valid.
Your code is completely in control rather than having
to come back and update the UI if the validation failed.
Here, we're implementing the event in line,
but we could instead pass in the state and the update lambda
as a parameter to a composable, moving the logic up
to wherever it makes the most sense to handle it.
The key idea with declarative UI
is that you completely describe
what your UI should look like for a given state
and the framework takes care of changing the UI
when the state changes.
Compose can work with any application architecture,
but is an especially great fit
with unidirectional data flow,
where your view model exposes a single stream
of screen states.
This can be observed by your Compose UI and passed down
to individual components as parameters,
each of which only receiving the state it needs
and therefore only needs to be updated if this data changes.
Producing a single stream of view state objects,
helps centralize where state changes can be made,
making it easier to reason about the entire state
of a screen and reducing errors.
This pattern also makes your composable straightforward
to test, as they're all completely controlled
by their inputs.
So we looked at how the declarative paradigm makes it easier
to write UI.
Built on top of these principles,
Compose offers a rich toolkit of UI components.
Jetpack Compose implements the material design components
and theming system.
It offers all the components that you need
to assemble your applications: cards, draws, fabs and so on.
Basic components like text display and input, buttons,
and so on, all follow material styling out of the box.
Compose also implements material theming,
allowing you to systematically customize all components
to your brand by supplying your own colors,
shapes, and type styles.
Compose also offers
a new simple but powerful layout system.
It's based on rows and columns, which are roughly equivalent
to horizontal and vertical linear layouts.
Unlike the view system, however,
the Compose layout model prevents multiple measure passes,
making nested layouts performant.
It offers an implementation of constraint layout
for expressing more complex layouts,
but it's also far simpler to create custom layouts.
Performing your own measurement and placement
to achieve just the right layout
is as easy as implementing a function.
This stack of grid from one of the official samples
is achieved in just a few lines of code.
One of the biggest improvements, in my opinion,
is the new animation system.
It's much, much simpler to use, offering really powerful
and simple ways to bring motion to your UIs.
We're also working to bring motion layout to Compose.
Testing and accessibility
are first-class citizens in Compose.
They're both built on a semantic system
which creates a parallel tree
to your UI hierarchy that describes it,
providing more information
to accessibility services, like content description labels,
or helping you to match UI elements to assert against them.
Compose offers a dedicated testing artifact
that has been built to maximize testability
and offer easy APIs for testing composables in isolation.
The test rule also exposes the clock
which drives the UI updates
and offers APIs for controlling it.
This lets you have full control on your tests,
even testing animation code.
Compose is entirely written in Kotlin
and takes advantage of the great language features
to build powerful, succinct, and intuitive APIs.
Coroutines, for example,
enable us to write much simpler async APIs,
such as describing gestures, animation or scrolling.
This makes it easier to write code
that combines async events like a gesture,
which hands off to an animation,
or with cancellation and cleanup
provided by structured concurrency.
Kotlin also brings a powerful ecosystem of tooling,
for example, making it trivial
to extract your UI components
Into a new function, promoting reuse.
To really understand how these features come together
to make it easier for you to build UIs,
let's walk through an example.
This is Jetchat,
one of our official samples available on GitHub.
Let's look at this conversation screen
to see some of these features in action.
Let's start by building an individual message item.
We'll write a new composable fun
which accepts our message model object as a parameter.
We can use the text composable again here
to show the message's content.
For the background,
we can surround the text with a card composable.
We also want some spacing around the text.
To add this, we can decorate any composable
with its modifier parameter.
There are many different modifiers available
for customizing layout, drawing, interaction and more.
Here, we're using the padding modifier to add some space.
We want sent and received messages
to have different background colors.
We can achieve this by passing in a Boolean parameter
and conditionally set the card's background color
based on this.
Here, we're looking at colors from the application theme.
Notice that setting the background color
also sets in an appropriate content color
so that text is light on dark backgrounds
and dark on the yellow background.
You can, of course, customize this behavior,
but Compose makes it easier
to support different themes such as light and dark.
Speaking of light and dark, in Compose, it's simple
to retrieve colors from the theme.
So this composable already supports dark and light theme.
To help develop and verify this,
Android Studio offers the ability
to create previews of any composable.
We can add a new function annotated with app preview,
and Android Studio will render a preview
on the design surface.
No need to install the app
or navigate to where the component is used.
It provides quick feedback as you make changes.
You can add as many previews as you like.
For example, permutations of light/dark mode
and different senders.
Notice that we want messages
to not quite fill the entire width
and to be aligned differently depending on the sender.
We could implement this here using modifiers on the card,
but a more flexible approach is instead
to accept and modify parameter ourselves.
This allows the caller to specify this,
making the composable more flexible
and easier to reuse in different places.
So we add a modifier parameter to our composable,
defaulting it to the empty modifier object
and pass this along to our card element.
The caller can then decide how to lay it out.
Notice how multiple modifiers can be chained together
to build up complex functionality.
This can be read as an ordered list.
Apply some spacing,
then apply a maximum width,
then align the element to the start or end.
So far, we've created a single message item,
but on the screen, we want to display a list of messages.
Let's create a new composable for this
which takes a list of message objects.
Compose offers a LazyColumn composable
which lays elements out vertically like a regular column,
but it only lays out enough elements to fill the viewport,
like a recycler view.
This makes it very efficient
for displaying a window of a large data set.
We can pass a list of objects
to an items block and how to render each individual item.
And that's it; no adapter or item layout files to manage.
Our design calls for actions you can perform on each item
displayed when the user taps on them.
To track whether an item has been tapped,
we can introduce some local state
into our message composable like this.
There's a lot going on in this line, so let's unpack it.
"MutableStateOf" wraps a Boolean
and composes automatically observable type.
This is what causes any readers to be updated
if this value changes.
"Remember" gives a composable function memory
so we can save or retrieve local state for this function.
The "by" keyword unwraps the observable object
so that we can treat it as a raw Boolean.
We toggle this state when the user clicks on the message
and conditionally show the row of actions.
The "AnimatedVisibility" composable automatically animates
the entrance or exit of its contents.
Lastly, we want the users to be able
to customize a conversation's color scheme.
We saw previously
that this was using the theme's secondary color.
In the view world, themes were immutable.
It was impossible to customize them at runtime.
Theme colors in Compose are modeled
as a collection of named colors.
So to create a new dynamic theme,
we can accept an override parameter,
copy the color object, and replace the secondary color.
We can even animate this theme change
with the animateAsState function.
Hopefully that illustrates how Jetpack Compose
makes it faster and easier to build UI,
and that's just scratching the surface.
Jetpack Compose is built
to co-exist with the existing view system.
You can embed Compose within Views
as well as hosting Views within Compose.
This can be especially useful for displaying content
not yet built with Compose, such as ads or a map view.
This can also help you gradually migrate to Compose,
adopting it at your own pace.
In addition to view interrupt,
we provide integrations with other key libraries.
So Compose can work
with your existing application architecture.
No need to start from scratch.
We also provide wrappers for common image loading libraries
and adapters for converting material
or AppCompat XML themes into Compose.
Hopefully, I've shown today how Jetpack Compose
makes it faster and easier to create great UIs.
With Jetpack Compose now in beta,
it's a great time to learn Compose
and get ready to adopt it.
Check out the Compose pathway,
which provides a curated selection of resources
for you and your team to learn Compose.
We also have a number of sample applications
and new docs available.
Join us on Slack to talk
to the team and the Jetpack Compose community
or file bugs and feature requests.
We love hearing what you're building
and how we can make Compose better for you.
Thanks for watching.
(soft music)