[MUSIC PLAYING] LIAM SPRADLIN: Hi,
my name is Liam. I'm a senior design advocate
at Google on Material Design. RODY DAVIS: And my name is Rody. A senior developer
advocate at Google. LIAM SPRADLIN: Today, we'll
be talking about translating a design into code. We'll be breaking down how to
take a design from a Figma file to creating adaptive reusable
components for Flutter that target all six
supported platforms. An important thing
to remember is that adaptive design is not just
about the breakpoint and device target. When building a truly
adaptive experience, there's a whole matrix
of options to consider. For example, a tablet with
a keyboard but no mouse, a desktop with only
a touch screen, or a mobile version
that's running on a desktop in free form. With Flutter, it's easy to
create platform adaptations and build a flexible UI
that's capable of targeting the correct platform
and input method. To illustrate what this
means, particularly as you build adaptive
apps with Flutter, we've made a sample study. A recipe discovery
app called Pesto. It's an app that has several
different user journeys that can take place across
different device types, input modalities, and contexts. For example, you might enter
and manage your recipes on a computer screen, browse
for inspiration on your tablet, and carry out the recipe on an
ambient device like a Nest Hub. From a designer's
perspective, we build layouts and experiences
for apps like Pesto with a ton of contextual
information in mind, meaning that our intention for
the experience, how it works, how it feels, the
character of the design is embedded deeply into the
artifacts of our work, often without explicit markers for
the people we're working with to bring it to life. Things like the grid structures
that underlie our layouts, the navigation
interactions we intend, theming and visual
customization, and the motion and transitions
that link everything together are just a few of the
key aspects that we need to accurately and
reliably convey to developers. In this talk, we'll
walk through some of these key elements in
reference to our example app and talk about how Flutter
can preserve and convey your design intent. RODY DAVIS: From a
developer's perspective, we tend to think about
designs as a set of components that we can post together in
higher order UIs. Flutter gives us the ability to
build declaratively on every frame and any size. When targeting six
platforms, there's a lot of things we need
to consider such as input methods, touch targets, keyboard
events, native menus, file handlers and more. We also need a translated
design in Figma with dummy data into a working demo
using mock data. This may be a
tempting step to skip when building with Flutter. But by starting
with dummy data, you can decouple your UI
from the business logic and start working off of a
few model style presentation. And finally, we need to think
about all the animations not shown in Figma or even
implicit ones for transitions throughout the app. Having a way to quickly test
various animations is critical. One exciting update is the new
Material 3 widgets for Flutter. There are multiple
widgets that are included. And we'll be going over
how to use them today. As we go through
this talk, we're going to be building the
Basil Theme together. This will cover theme
extensions, custom fonts, dynamic color,
and widget themes. First, let's create the
Material 3 Theme class. Here, we're creating a immutable
class with some brand colors that the design defines for
us in a simple constructor. Brand colors, if you're
not familiar with, are colors associated with
the company and colors that make up the logo
or related assets. For Google, this is the four
colors that make up the G logo. Next, we create a callback for
creating the theme data object. And we'll use that to pass to
the theme for the Material app later. Available in the current
version of Flutter, we can enable the use
Material 3 theme override, which we will use for
the new color system and update any
supported widgets. After we have
defined the class, we can then import it at the
entry point of our application and instantiate the class. We then just call the
two-theme data method and build the Basil
theme, which we then provide to the widget tree. Later, we're going to be
talking about ways in which we can theme the application
with just the default widgets and theme overrides
which allow for better reuse and extensibility. Next, we can take
advantage of the theme data feature called Theme Extensions
released in Flutter 3.0. To create a theme extension,
simply extend the class we just created with
the theme extension class and the type of our theme. This is required for when we
pass it to the theme data class later and for providing
it to the widget tree. We only need to
override two methods. The first being the
copyWith method. This is for when
you're inside a widget and you want to override
a single property and get a new theme back. Next, we can just
override the alert method, which is used for animation. Just check to see if
the type coming in is the class we're expecting
and work through all the inputs. Now, back to Liam on typography. LIAM SPRADLIN: Speaking as
an interface designer who's also a type designer,
it's important to me to note that type or text in
all its expression is interface. It serves so many
purposes in an interface from delivering content
to aiding in navigation, all the way to elements that
are nontextual but still typographic, like icons. One big consideration is how
you choose the type styles that best serve your
content and layout. In space-constrained places,
like navigation components, the right type style can allow
you to communicate more clearly with users. Other type styles, like those
found in materials type scale, can help you create and
maintain consistent information hierarchies throughout your
app and across devices. Titles, captions,
body text, even inline links can
instantly be recognized by users if your
type scale carefully considers their styles. Making more nuanced choices
about baseline grids, columns, spacing, and alignment
can also contribute to the overall character
of your experience, making it strict,
geometric, and modern, or allowing it to be loose,
free flowing, or even playful. RODY DAVIS: From a
developer's perspective when translating a design,
it is important to preserve the semantics around typography,
emphasize, spacing, and type scale. It can be easy just to
stick with the default textile in Flutter and
just change the font size or alignment as needed. But there's a lot we can
do to really polish UI. For most cases, you can get
away with using the text widget in Flutter. But sometimes, a design
will have a bolded word in the middle of a sentence. And for that, we can use
the rich text widget. Also, in text-heavy
apps, there's a dynamic list and styles
such as in a markdown source. And for that, we can
use the Flutter markdown package which will convert
our inputs into widgets and rich text. Both of these are reasons why we
want to have great custom text themes. Often, a design will have
multiple font pairings that corresponds to titles and
headlines or body and text. Flutter gives us the text
theme class, which we can then use to define our type scale. And we can include any number
of font families for the levels. To actually load
the font, we can either include the font
assets directly in our app and define them in
the pubs of a Caml or use the Google
Fonts Pub package and dynamically load them in. Using the Google
Fonts package allows us to test different font
pairings before optimizing for production build. First, we need to
import the Google Fonts package in the file where
we defined our theme. Next, we can create
a new method, which returns the base theme
data class, which we will then use for other contexts such
as light and dark later. We can then import these two
fonts defined in the design spec and create a
merged theme, which we will then use to pass
to the text theme class. Note that it is possible to
override just a single level or groups of styles depending
on the design intent. This package just
requires internet access. So make sure to give
the mac OS target if running the correct
entitlements defining the package. That's it for typography. Next time we run
the app, we will see Roboto updated with the
new correct default textiles. LIAM SPRADLIN: One
thing that we really focused on in the design
updates for Material 3 is color. And a lot of the
changes and improvements made to the material
color system were motivated by creating
and preserving design intent. Rather than defining
each individual color for each individual
visual element, we established a
system that generates a comprehensive
and cohesive color scheme based on the
relationships between colors. This way, design intent for
the meaning and function of different elements that can
be communicated through color can be preserved
throughout the process of building an interface. In Pesto, a recipe
screen can even be themed based on the image
associated with the recipe. In this case, the
creamy pesto pasta translating the users
or the recipe writer's unique intent into a cohesive
version of the design intent that you've established
and implemented. RODY DAVIS: From
developer's perspective, Flutter makes working with
dynamic color easy thanks to a great pub package
called Material Color Utilities and the
extensible theme data class. Lets build a custom
color scheme together. Let's add the Material
Color Utilities import above our theme to get
access to the HCT color system. The Flutter SDK already
depends on this package. But you will still
want to add this to your pub spec dependencies. Let's create a
method that returns a scheme that we can use to
create a custom color scheme class. Sometimes, a design is only
based on a single color. And for those instances,
we can use a color scheme from seed, which gives us
a beautiful generated color scheme using the
ACT color space. For Basil, we have
multiple brand colors that we need to use to create
custom tonal groups from. First define a base palette
that all tokens can inherit from if not overridden. Next, create the
tertiary tonal group and note that the palette is
returning the primary palette to provide the chromatic tonal
group that the design was intending. Finally, create the neutral
palette, which will also be used to override tokens. The color palette class
comes from the Material Color Utilities and is a group of
palettes that are derived from a single source color. Next, we will be adding
tokens to the scheme class. You may not be familiar with
the scheme class or the concept of design tokens. But the scheme class is a class
in Material Color Utilities that has a one-to-one
relationship with the color scheme
class in Flutter. All these parameters
are designed tokens, which are theme
values that can be passed down and inherit from in subthemes. Here, we're just going to go
quickly through the scheme and define the tokens
from those palettes. For each of these
tokens, we pulled them from the tonal palette
and get a color based on a given tone of brightness. To learn more about the new
token's material design, you can go to m3.material.io. Next, add the overrides for
background, error group, outlines, and surfaces. Finally, add the overrides for
surface variant and inverted colors. Note that for the
dark theme, there needs to be different
tones use for these tokens. For most cases, you will not
need to create a custom color scheme. But this is an example
of all the things done for you when you use the
color scheme from seed method. We can also create an extension
method on the scheme class to return a color scheme that
the theme data class expects with a given brightness. Now, we can add the color scheme
parameter to our base method and pass the color scheme
to the theme data class. When overriding a token
such as scaffold background, sometimes, the design
will use a brand color instead of a derived
token in the scheme. For this token,
we're checking to see if its light, brightness,
and returning the brand color defined earlier. Finally, we can compose
the complete color scheme and the theme data class
in two-theme data method while making sure to
pass the brightness. Sometimes, we want to
pull a theme from an image and have it match
the main theme. For example, a music player
app can pull from the album art and change the
background to match. This color could be anything. But thanks to the
Material Color Utilities, we can make sure that the color
will meet contrast requirements and have a color scheme
that will work with our app. In Pesto, we can use the
image on the detail screen to create a content-based theme
that matches the top level theme. To do this, we can
create an image theme class, import Material Color
Utilities, and the image package. Next, we create a method to take
the image defined at the path, get the bytes, and convert
them to an RGB pixel array with the image package. We can then implement
some basic error handling. Then we can implement
the source colors from image method that
takes the pixel data, quantitizes the result,
and returns them to the ranked
pixels in the image. Now, we can create
a color scheme from image method that takes
the current color scheme and blends it with the primary
of the top color in the image. We then can use that seed to
generate another color scheme which we then return. This blend method is important
because otherwise, the color could potentially clash
with the main theme. Finally, we can
use the method just to find to create a
build method, which returns a FutureBuilder
and creates a color scheme from the image. It then passes it
to the theme widget, which will override the theme
data in the widget subtree. Once we have defined all
the colors and typography, we need to create individual
widget theme overrides. This will help reduce the
need for custom components or theming in the build method. Learning how to define
these overrides properly will help the application have
an experience that properly reflects the design intent. First, let's create
the TabBarTheme-- then the FloatingActionButton
override. Next, the
NavigationRailThemeData. Note that these are just
applying color overrides based on the design. Finally, override the
AppBar and ChipTheme. There are many widgets
that we did not override. But it's because we're
not using them yet or the default to
Material 3 appear correct. LIAM SPRADLIN: Of
course, the ways that people can use your app
will depend on where, when, and how they're using it. This is where it becomes
really important to account for different input modalities. If a user has a keyboard,
switch, mouse, touch, or even voice to interact
with your app, it should be accounted for in the
way your design is structured. The way your layout is
structured, specifically how users traverse both
visually and physically from one item to the next is
crucial information that's not captured
in one static mockup and requires extra documentation
to successfully translate into implementation. And this might change
based on what device your app is running on. Layout density might be lower
on an ambient device that's viewed from far away and has
less need for touch input. This will mean quicker
traversal through fewer onscreen elements. On a phone, the
density may increase. But things like
touch targets become more important to consider. Across devices, the
navigation structure may change as you gain
or lose screen space. RODY DAVIS: From a
developer's perspective, we should be mindful about
where the application is intended to be deployed. In Flutter, you can define
multiple entry points depending on the target
you're building for. Rather than building a
single UI package in binary that's meant to run
in all these contexts, you can compose the
UI and functionality depending on which top-level
app you pass to runApp. In your theme data class, you
can pass adaptive platform density Enum to the
visual density override to create a more compact
UI in these contexts where input is more precise. And when it comes to
traversal and touch targets, it is important to remember that
building a great accessible app is good for keyboard navigation
and accessible touch targets. Flutter has a good
focus traversal. And if you're using the
default focus nodes defined in most widgets,
you can test them by using the Tab and Shift
Tab keys to navigate. Make sure to define
tool tips and meaningful semantics where possible
throughout the widget tree. For navigation, you can
use the go router package to split up the
routes into files and have fine-grained control
with the new Navigator 2.0 class. This is what the Flutter
app looks like running without our theme applied. But when passing the
theme to the Material app, we get a beautiful Basil
Material 3 theme in Flutter. The theme data class
is very powerful and allows us to create
truly custom themes that match the brand or design spec
handed off to the developer. Sometimes, a theme for a
widget is not available. And for that, we can use
extensions and custom widgets. LIAM SPRADLIN: If you still
find the concept of building a custom theme challenging
or just need somewhere to get started with your design, check
out the Material Theme Builder and export your
theme for Flutter. You can also learn more about
Material 3 and Material.io. Thanks for watching. [MUSIC PLAYING]