Compose by example

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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.
Info
Channel: Android Developers
Views: 38,667
Rating: 4.9245281 out of 5
Keywords: 11 weeks of android, ui, user interface, android 11, android 11 beta launch, android news, android updates, android latest, android featured, google developers, android developers, android developers news, android developers updates, android developers latest, android, google, Nick Butcher, dynamic theming, design system, animation, compose modifiers, test theme, styling, material design, material theming
Id: DDd6IOlH3io
Channel Id: undefined
Length: 22min 8sec (1328 seconds)
Published: Wed Aug 26 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.