[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]