[MUSIC PLAYING]
ALEX VANYO: Hi, everyone, I'm Alex,
a developer relations engineer.
MATVEI MALKOV: And I'm Matvei, a software engineer working
on Jetpack Compose.
So today, we are going to take a look
at how Compose can help you build adaptive UI on all
your apps no matter the orientation device you're
running on or the window real estate available,
though there are much considerations
to keep in mind when building apps at each player.
Speaking of players, today we are
going to zoom in bit by bit.
We'll start from the application level, which is
the outermost level for a UI.
Then we'll go to the screen level, which is still,
you get the majority of the window,
but you arrange items and components based on the window
size available.
And then we are going to take a look at the components
and how the choice of components can help
you have an adaptive UI by default.
Let's start from the application level.
In this level, you basically have your window
to render your whole UI into.
The thing about this window is that this window has obviously
a size, height and width.
And this width and height, they can change dynamically.
And it will change as your app is running.
And you have to keep these considerations in mind.
Let's start first to see what we should do
and what we should not do with this knowledge
that the window size can change.
Let's start from breaking the rigid assumptions
of having isPhone or isTablet Booleans in your application.
Going to the extreme, you can have complete different UI
files, code structures, navigation patterns that can
result in bug fix and the bugs.
And bug fix is hard.
You have to maintain the whole separate structures and files.
And most importantly, this doesn't represent the patterns
like how the users use our UI these days,
our applications these days on all devices available.
For example, those are the patterns you should avoid.
And this is, for example, the tablet
in which user has decided to put our application base
side by side with another one.
This results in our app having a window size which
is way closer to the phone than to the whole tablet available.
So it's way more practical to work with the window size
and not with the device size or something else.
Thankfully, in Compose, we can use it with ease.
We can just treat our window size
as an input to our composable.
Because of the nature of Compose,
when the window size changes, the composables are composed,
and you can make some UI-based decisions
based on this window size.
As you might have heard, we took it a bit further and made it
a library, which we call Material 3-Window-Size-Class.
This library provides you with useful methods
like calculate window size class.
This method takes the numerical size of your window
using the Jetpack WindowManager APIs
and converts it to what we call window size classes, which
is a set of our predefined width and height breakpoints
which represent the majority of devices or window sizes
in a vertical category.
For example, compact window size width of the window
can represent the majority of the phones,
but also, if you place the application side
by side within the tablet, you also
get the compact window width size class.
So taking this library, we can use it in Compose
just like this.
We take it as an input to a composable,
and then we make UI-based decisions.
Because we're talking about the application level,
the UI we should be concerned about is mostly navigation.
So what we recommend to use bottom navigation
for the compact window size classes,
and then we recommend to use navigation rail for the rest,
meaning the medium and expanded window width classes.
We have this doc called Navigation Compose
on every Size Screen by Jeremy that goes more
into the details of how to handle all the bits
and pieces of navigation in your application given
the large screens.
ALEX VANYO: All right, now let's zoom
in a step to take a look at the level of the screen.
Your app probably has many screens,
each with a recognizable set of UI
to provide some functionality to the user.
You might have a home screen, a search screen, a setting
screen.
These are each going to look differently from each other.
So what's a generic framework that we
can use to construct a screen that
is going to be shown across these various devices
and window sizes?
Well, when you're creating a screen,
you're building a logic-- or sorry,
you're building a screen that's rendering
UI as a counterpart for the logic for that screen.
So from a state holder, you'll get
the state, list of information, loading information, error
states, and you'll also pass back events
to the state holder representing user actions.
With both of these two things, you
can create a bunch of basic UI that you can display.
These will be your basic composable functions,
things like text, buttons, and checkboxes.
Components can also be combined into other components.
You might have a reusable list item, a reusable card item,
and you might be displaying multiple of the same type
of component to display a list.
All right, so we have the state we want to display.
We know what the user can do on the screen.
And we also have all the individual pieces of UI
we want to show.
But we're still missing one thing,
and that's the screen itself.
What you need to do is lay out all these components together.
And you should use the window size class
as an additional input into this decision.
So at a compact width, we can render all of our components
vertically in a column one by one.
But at an expanded width, we could use those same components
but use an alternate arrangement that
makes better use of the screen space available.
So here, we have now a single screen
that's rendering adaptive UI, same data, same actions,
but alternate layouts.
This is the primary role of the screen-level composable,
managing the arrangement of components
with the same data and actions where
that arrangement is partially based on the window size.
All right, with this in mind, let's take a look
at a simple onboarding example.
Here, we have an image of my cat,
some contextual information, and a button
to continue on to the rest of the screen.
The logic for the screen is super straightforward.
On all devices and window sizes, you
should be able to see the info and then continue on
to the next screen.
So let's see what happens when we run a larger screen.
Oh, no, the button is completely cut off here,
and it's impossible to continue on to the rest of the app.
So we can clearly do a better job here
both to make it be functional and to also
use the window size to choose a better overall layout.
So let's start with the current code
with the column of the image, the text, and the button.
The very first thing we can do to fix that showstopping
bug where we couldn't even continue
on to the rest of the app is to make the column scrollable.
So this will work, but it will not
be quite as good as we can do.
We can do better.
So the main issue here is that our image is filling up
the entire width of the window.
This sort of works if you're in portrait where the height is
much taller than you are wide.
But as the aspect ratio changes closer and closer to landscape,
that starts to break down.
Instead, what we can do is apply a weight
to the image inside the column.
This allows the image to resize and ensures
that the button is always going to be visible without having
to scroll.
All right, one more thing we can do, though,
is when we're at an expanded window width,
we now have enough space that we can rearrange
the components to make better use of the screen space
available.
So at an expanded width, we can instead
display the image side by side in a row
instead of in a column.
So putting all these things together,
we now have a UI that is both functional
and looks good across window sizes.
And as the window size changes, the screen
will adapt its layout to ensure all the content is displayed.
MATVEI MALKOV: OK, now let's take a look
at the little bit more complicated examples using
the same framework.
These examples involve companies that
move in relation to each other and the window itself,
and the arrangement changes as the window size changes.
We identified these three patterns
which we call feed, supporting panel, and list-detail, which
are the most common throughout the whole variety of apps
you can build.
We call them canonical layouts, as you might have heard before.
We treat them as canonical layouts
because they are our opinionated way on how you can arrange
components on a screen and how you
can change this arrangement based on the window size that
is changing as well.
Let's go through them one by one to learn more.
So feeds is the amazing canonical layout
that displays the set of items with roughly equal importance.
You can see on the map here, we have a staggered grid,
which is also the API available in Compose 103 as experimental.
The real-life example of this might look like the following.
The video is running.
So on the video, you could see how--
thank you-- how now an Android app is being resized,
and we switched from lazy single column composable
to a staggered grid.
And as more as I resize the window,
the more cards join the horizontal row
while still being scrollable.
This is basically the staggered grid
as well we're using from Compose.
Also, here you should be able to see the power of the Freeform
Emulator, which allows you to test
your UI while the window is dynamically changing,
which is very, very useful.
So just to recap quickly, the feed
is very useful when you have a set of items.
It can be used for videos, cards, images, and so on.
We have some APIs available.
LazyVerticalGrid is a stable API available in Compose
to use this or LazyVerticalStaggeredGrid
if you are feeling a little bit more experimental, which
is an experimental API available in 1.3.
In this example that is now in Android,
I use adaptive grid strategy, which
allows you to basically get the resizing, more cards joining
your width of your window as you resize for free by using
an adaptive arrangement both on just grid and a staggered grid
as well.
OK, onto the next one, which is a supporting panel.
Supporting panel gives the majority
of the content to the main portion, which
is content in focus, and then the rest,
which is a supporting panel, which is a supporting
part of the window.
Remember these video cards example we just
built a few minutes ago.
Well, this is basically a supporting panel
which we can build using Compose if you have the right tools.
Let's find out these tools.
First of all, we calculate the window size classes as before.
The important thing to note here is
that even though we introduced window size classes
at the application level, they are still useful at the screen
level as well.
Why?
Because at the screen level, you still
get the majority of the window, and so it makes sense
to make some distinctions based on this data.
So we calculate our window size class,
and we also calculate display features,
which is the object containing information
about folds and hinges to better support foldables.
This calculate display features is
our API available in Accompanist Library.
Accompanist Library is our laboratory-like library
available on GitHub.
It contains some useful bits and pieces
that are not yet in Compose.
Go check it out.
Try these building blocks we'll see right now and later on
in this talk.
Let us know what you think file issues,
and go check the second link, which
is FAQ that explains why Accompanist exists
and why you might use them.
So we're taking this display, the window size class
and display features from Accompanist,
and we're building our supporting panel.
First of all, you can see the TwoPane, which is the building
block also in Accompanist, Accompanist.tif, which
is the whole set of adaptive building blocks.
So you take this building block.
It allows you to arrange two pieces of content
either vertically or horizontally.
And it also supports foldables as we can see later.
So this is amazing building block for supporting panel.
We take this TwoPane.
We pass our first slot as video, the main content,
and then we leave the second content for the second slot
for the list.
Now, the most juicy bit, we pass the strategy.
And we pass the strategy based on the window size class
available.
So for example, we might decide that it makes sense for us
on a compact window width to show them one by one
vertically and then arrange them horizontally
on the medium size class.
We can also realize that, for example, for the expanded
window width, we have modules that are available,
and we can give a little bit more to the video.
So that's where you can see 0.7 for the expanded one.
Last but not least, display features--
remember, I told you about this.
So on this amazing, amazing photo,
you can see the tabletop mode on a foldable.
It's basically a foldable that is folded 90 degrees.
And passing displayFeatures will make sure
that TwoPane respects this well.
So if your user decides to put your foldable 90 degrees,
the TwoPane will arrange them 50/50 so both of the panes
are accessible properly.
So to a little recap, supporting panel
is a very versatile canonical layout, to be honest.
As long as you have any main content and any part
of supporting content, you can use this canonical layout
to support better large screens.
TwoPane is here to help.
It can support vertically, horizontal, split.
You can tweak the values as your use case feed.
And you can specify what folds to support,
what hinges to support, and in what orientation, so pretty
flexible.
All right, the last one is list detail.
The list detail is the canonical layout
which allows you to show either list,
the selected item from this list, which is a detail,
or both.
Because we are talking here a hierarchical UI
where you can have a list and detail
and basically detail depends on the item selected in the list,
the navigation part might present some challenges
for this canonical layout.
Please go check the talk Navigation Compose
on Large Screens.
It's thoroughly covered there how
to handle the backstack and all this navigation story there.
On the UI part, we can use TwoPane again
because basically you can show the two
panes on the list and detail.
So this is amazing building block for our list detail
as well.
And a bit of recommendation from our size,
we recommend to use only list or only
detail on the compact and medium width size classes
and show both on the expanded one
as you have more real estate.
The sample for the supporting panel and for the list detail
and for the feed are available on this link.
This is the repo of our samples we host on GitHub.
Go check it out.
Try it in your application.
Let us know what you think.
And hopefully, those samples should
allow you to get to the [INAUDIBLE] place
better and faster.
ALEX VANYO: All right, so we've looked at the app level.
We've looked at the screen level.
Let's zoom in one more time now to talk
about individual components.
Individual components should also
be flexible and work across a different range of sizes
and using the correct building blocks to do so.
Components that are flexible will
help with optimizing for larger screens,
but they will also help to alternate amounts of space
due to different languages, different font
size, different data, or any combination of the above.
These more flexible components are also
more reusable because it means you can plug them
in to different areas more easily without running
into issues.
So let's first take a look at an inflexible component.
So as you can see here with these cards, if there's not
enough space or if the font size has been increased,
the chips at the bottom might be cut off, which isn't great.
So let's take a look at how we can fix that.
We'll start with our overall code
for the card, which has a column of the header, the title
and the body, and then the chips at the bottom.
And we know those chips aren't scaling very well.
So let's take a closer look at those.
Right now, we're displaying the chips in a basic row.
So this seemed to work if we only had one or two chips
and if we had enough space.
But as you can see, if we have more chips or not enough space,
this starts to break down.
We can fix this by using a more appropriate component
for the job.
FlowRow is another component from Accompanist
that allows us to do so.
FlowRow will break the chips onto additional line
if possible or if needed to display all the chips
without being clipped.
So as you can see here, now all five chips
are displayed across two rows.
So going back to the cards, we can now
see that the chips are no longer cut off,
even on different screen sizes and font sizes.
Setting up a set of previews like this
is super helpful to verify your behavior
as you're making changes and to test across different amounts
of these configurations.
And also, if you want to apply these configurations
to multiple composables, be sure to create
a multi-preview annotation as well.
One other thing to keep in mind at the component level
is that devices with larger screens
also mean different input devices being
used to interact with your app.
So if you're using a mouse, things like hover states
and mouse clicks are important.
Components from composables handle these interactions
automatically, so things like the simple interactions
automatically.
So things like if you're using modifier.clickable
or indication, you should get some of these basic support out
of the box.
But there's a lot more that you can do here
to make full use of the flexibility
that these different input devices provide.
So be sure to check out the other ADS talks on keyboard,
mouse, and stylus.
All right, so we're going to zoom back out now.
Think about how you can make each level of your app
more responsive.
If individual components can be intelligently displayed
across a large number of configurations
while making less assumptions, they're
going to be more reusable and robust
to different combinations of size, locale,
and other factors.
These reusable components are also
easier to organize in different ways
using the screen space available to structure a screen
that still provides some functionality to a user.
And at the app level, you can see the benefits
of separating state from UI.
You have a single app and navigation structure
that's representing the same data and features to a user,
but it's optimized to be even more useful on their device.
Thank you very much.
And as always, happy composing.
[APPLAUSE]
[MUSIC PLAYING]