[MUSIC PLAYING] CRAIG LABENZ: When I started
my software career as a web developer, one of the
first things I learned was not to mix my
semantic information with my presentation rules. On the web, this
concept comes to life by storing your semantics in
HTML and your cosmetics in CSS. Of course, you can inline your
styles directly into your HTML. But if you do,
(IMITATING GANDALF) you shall not pass (NORMAL
VOICE) code review. A decade later, when
I found Flutter, I knew I liked what I
saw, but the common mixing of my raw information with its
presentation was unsettling. I looked up how
to stop doing this and was, of course, pointed
at Flutter's theme system. I thought, neat. And then I dug into it a
little bit and thought, what? And so today we're going
to talk about a few tricks and a new API to squeeze
the most juice out of your apps theme and make sure
that your custom widgets rarely require inline styling. Hello, widgets department. Who's this? Raw styling code? I told you to stop calling. To start, let's explore
how material widgets use Flutter's theming
system to figure out how to dress themselves. Put simply, most
material widgets have three places they
look when resolving each of their cosmetic attributes. The first is any raw parameters
exposed in their constructor. Sometimes this is in top-level
color or padding parameters, and sometimes it's in
dedicated style parameters that wall off all the details. Either way, these are all
those random styling parameters we all use every day. At least for me, the
most common situation is this, where I start
from my existing theme but modify it in some way,
because my current purposes were hard to capture in
the actual theme itself. The second place
material widgets look is up the tree to an inherited
theme widget and its associated ThemeData instance. This is what Count
Dracula taught us in his guest Widget
of the Week appearance back in October of 2021. Pray that you remembered it. Note that this is
what happens when you supply a theme or a dark
theme to your material app. Internally, it sticks
those in a theme widget. The third option, if no one
has picked up the phone yet, is the default theme, which can
be found at the ThemeData.light or ThemeData.dark
factory constructors. And this is what produces
the infamous blue-white color palette of the counter app. Within a given material
widget, the code that looks in these three places
will often look like this. Step one-- the widget checks
any of its own parameters. This part is optional
because not every widget has such parameters
for every detail. Step two-- the widget
asks the theming system. And step three is sneakily
wrapped up in step two, because the material widget
app adds a default theme if you don't. So this will return something. And this all happens on
a per-attribute basis, which is why you can use the
convenient TextButton.styleFrom method that we talked
about in the last episode. In this scenario, our TextButton
will resolve its background color in step one, and
for everything else, continue on to
steps two and three. Now, this is all good and
fun, but here's the rub. Material widgets are hooked
into this pattern directly, so anything they
care about just so happens to appear in one
of the theme variables. How convenient. It's almost as if
the same people wrote both sides of that code. But how does that
help your widgets? If you're designing
heavily custom controls and want this same
ordered approach, which allows most or all
of your styling code to stay out of your raw
widgets, you're out of luck, until that new API
I mentioned earlier. Coming in the next
stable release of Flutter is a new system called
ThemeExtensions. To start, use the extensions
parameter and either a brand new theme or a
copy of an existing theme. Into that variable, put an
iterable of ThemeExtensions and use the dynamic type. Next, instantiate a class
of your own creation that contains whatever cosmetic
details are important to you. Imagine you're working on
an all new widget, which is like a card/hero image hybrid. To support its build method, you
decide to introduce a HeroCardStyle class. This means you can use the same
tricks above in your HeroCard build method and check for all
the variables incrementally. The only the problem
is Flutter's ThemeData class doesn't know about your
custom HeroCardStyle class, so would have no idea
what you're talking about, which is where
extensions come in. Back to our Theme class,
register this HeroCardStyle as an extension. Of course, the class doesn't
exist yet, so let's write it. To be valid, our class should
subclass ThemeExtension and use itself as
its own generic. Trippy, I know. Obviously, it needs
to know about all of its relevant variables. And beyond that, there are
two methods we must override. The first is CopyWith, so that
our extension can play along with the ThemeData class's
normal copying mechanisms. And the second is lerp,
because all theme things need to know how to linearly
interpolate themselves. Finally, everything is in order
to finish our HeroCard widget. In its build method, check the
parameters you exposed first, then fall back to the
ThemeExtension by way of Theme.of(context).extension,
using our type as the generic. Finally, we have
a HeroCardStyle instance, and we can get to assembling
whatever widgets make up a HeroCard. And it's worth noting that
your ThemeExtensions don't have to be this fancy and
serve a specific widget. They could just hold
some extra colors and expand your
app's color palette. Before ThemeExtensions, it wasn't easy to keep your
presentation rules out of your custom widgets. But now, it can be done. Hopefully, these tricks
help you write cleaner, more predictable layouts. For more info on Flutter,
head to flutter.dev. [MUSIC PLAYING]