♪ [intro music] ♪
As a Flutter developer,
you're used to implementing features
by wrapping parts of your UI in new widgets.
For example,
maybe you start with a text widget.
And then you think,
"Well, actually this will eventually have an image and a subtitle as well.
Better use a
But, of course, that's not quite right yet.
A little elevation and rounded corners are always nice.
Time to wrap it in a
Better, but this also needs to be
Easy enough.
There's a widget for that.
Eventually, your UI looks pretty good,
which is when you remember
that this item will actually be rendered inside of a loop.
And now you've arrived at
one of the time-honored Flutter developer moments,
breaking up an unwieldy build method.
The first step in this process requires you, the human developer
to make a judgment call.
You must identify which code blocks are good candidates for re-use.
Attributes that lend themselves toward re-use are:
repeated sequences of code
and discrete sections of your actual UI.
Often, these two things go hand in hand.
In our example,
it's the
and all of its children that should be separated.
It both repeats in our code
and renders a contained section of our UI.
Great!
The second step is, of course, to actually move the code.
And this is the crux of what you and I are chatting about right now,
because there are two possibilities before you
with quite different implications.
The first option is to move the code into a
This is often called the "helper method" option
If you're anything like me,
you probably started doing this
when you first encountered a big unwieldy built method.
The second option is similar,
but instead of moving your code block into a method on the same widget,
you move it into an entirely new widget.
Similar but not the same.
Given these two options, which do you normally use?
Helper methods or separate widgets?
Again, if you're anything like me,
you evaluated your options and thought,
"Well, shoot, helper methods pass all of my variables for free,
and surely a helper method is more performant
than an entirely new class."
I'll use helper methods.
If that's you, I've got good news and bad news.
The good news is that you're still a great developer
and your users love your apps.
The bad news, sadly,
is that the answer here has been counter-intuitive.
And there are important reasons to prefer separate widgets.
Let's dive into it.
First and foremost,
remember that when
its entire
This means that if a user toggles a favorite icon
in the corner of a large piece of your UI,
having rendered that icon in a helper method
will require Flutter to rebuild the entire wrapping widget.
You may have heard that there are multiple trees
that Flutter uses to build your UIs,
and the widget tree is only the top one.
Creating a few more widgets in that top tree
is hardly ever going to slow down your app,
but unnecessarily rebuilding whole sections of your UI
can cause those deeper trees:
the element tree and the render object tree
to waste precious CPU time.
And what's worse,
consider what happens if you animate your icon between states.
Now your app is unnecessarily redrawing expensive UI chunks 60 times per second
for the duration of the animation.
when all it had to do was animate the pixels inside the icon.
So, instead of refactoring that favorite icon into a helper method,
use an entirely new widget up front,
so that when it toggles states,
Flutter is able to precisely target what it re-renders.
And for truly best results,
use const constructors wherever possible
as that allows Flutter to short-circuit the most amount of unnecessary work.
Congrats!
Now your app is running faster and everyone's happy.
It's time to write some tests for your UI.
So you open up that widgets file again
to remind yourself what to capture in a widget test.
One obvious candidate
is that the favorite icon we've been talking about:
changes color as expected.
Which piece of code would you rather write a test for?
The helper method version?
Or the separate widget version?
The separate widget version couldn't be simpler,
whereas the helper method version
requires you to reconstruct every dependency
needed by
and they were expensive,
so it's probably a lot.
As if that's not enough, there are other scenarios
where you can accidentally hold on to a stale
Consider this code which uses a
but accidentally uses a different name for one of the builtContexts,
allowing further nested code
to use an old, unreliable builtContexts.
Refactoring this into a separate widget can make the bug impossible.
Now our
and it's always the correct one.
Yes, it's true that creating new widgets
severs that free connection back to all of the available attributes,
Except, now that we've explored it more.
It never really was free, was it?
It always came at the cost of performance, testability
and occasionally, even accuracy.
When I was speaking about this issue with Remi Rousselet,
the creator of Provider, Riverpod, Freezed, and many other packages,
he summarized a situation like this:
"Classes have a better default behavior.
the only benefit of methods is having to write a tiny bit less code.
There's no functional benefit."
Thanks Remi,
for helping draw attention to this issue with some instructive Dart Pads.
And that's it, folks.
Next time you're breaking up an unwieldy build method,
don't be afraid of specialized widgets.
You pressing a few more keys during development,
might just save your users dropped frames during runtime.
For more info on Flutter,
head to
♪ [outro music] ♪