[MUSIC PLAYING]
TIYA CHOWDHURY: If you use the Flutter lint package,
which as of its 2.0 release added the use BuildContexts
synchronously rule, you may be seeing some new red squigglies
in your Dart code.
But what does this mean, use BuildContexts synchronously?
If you include this rule to your linting setup,
either by adding a dependency to your Flutter lint 2.0
or by specifying it yourself,
your editor will start complaining about situations like this.
Flutter knows that BuildContexts
are unreliable after an asynchronous gap
and it wants you to know that, too.
The first follow-up question in better understanding this is,
what are you talking about?
Remember that the BuildContext passed to a widget
is that widget's corresponding element in the element tree.
Also remember that while lots of our application code
is asynchronous, Flutter's build process is 100% synchronous.
There are no awaits, no futures anywhere in the assembly
of your widget tree.
And finally, recall the unique relationship between elements
and their widgets.
As Flutter developers, we mostly write widgets.
So it's easy to think that they are the primary object
in the relationship.
But widgets are ephemeral, living as briefly as one 1/20
of a second if your app is animating something
across a high refresh rate screen.
We see here that widgets come and go, but where possible,
Flutter keeps elements around.
This is a huge driver of Flutter's
incredible performance, despite how wasteful
it can seem to throw all these widgets away after only using
them for a few milliseconds.
Now I said Flutter keeps elements around where possible,
and this distinction is a part of why
asynchronous use of BuildContexts
can be so unpredictable.
There are many different scenarios
that can make Flutter decide that some of your elements
need to be refreshed, from scrolling, to navigation
events, to dragging and dropping.
But it's not worth trying to memorize them all.
For our purposes today, let's just
use the following rule of thumb.
The more dramatically your widget tree
changes from one frame to the next,
the more likely Flutter is in need
to update portions of your element tree.
And for every new frame, it's always possible
that a given widget's element has been removed from the tree.
Unpredictable events from your user
can force Flutter to reassemble the element tree at any moment.
So the only safe way to build your widget
is to avoid asynchronous usage of a BuildContext.
But this is just a new lint.
So if your code is anything like mine, when this lint arrived,
you would have found some places where
you were breaking the rule and probably your app
wasn't catching on fire.
So what's the deal here?
Part of the issue is that after an asynchronous gap,
your widget's BuildContext could be a ticking time bomb waiting
to explode the second you touch it,
or it could be totally fine.
Let's look at some code.
Consider this situation where a button's callback
submits a form and then pops the current page.
The new lint rule will tell you that this line is risky.
But why?
If you look up the lint online, you'll
see a suggestion to return early if your widget is
no longer mounted.
And this gets to the crux of the issue,
because certain events like user scrolling, navigation,
and several others may have already removed your widget's
element from the tree.
It's unsafe to call the famous .of method,
as passing stale BuildContexts to those methods can crash
your app.
Back to that lint.
The recommended fix is to do this.
The reliable way to confirm your BuildContext is still valid
is to check the mounted property on a state class.
And the fix works for this scenario,
too, because if this widget is no longer mounted,
something else may have already removed this page.
But does this always work?
Imagine this scenario, where instead of removing the page,
you want to show a snack bar that
informs the user of an action's outcome.
Does the if not mounted return check still serve our purposes?
Probably not, because even if the element has been disposed,
we still want the user to see confirmation of their action.
In that case, don't conditionally
return early if the widget is unmounted,
but instead hold on to the resources
you need ahead of time and then use them
after the asynchronous gap.
Here's a tricky one.
What about this code, which crosses an asynchronous gap
then uses an [? at-risk ?] BuildContext to read
the MediaQuery, update some layout calculations,
and call setState.
First and foremost, we know that if somehow the user
has navigated away from this screen
or scrolled past its widget in a list view that uses the builder
constructor, then our MediaQuery.of call is doomed.
Also, if this widget is no longer mounted,
then we definitely don't care about updating any measurements
for its children.
So it's safe to use the mounted check.
But what if it is still mounted?
Can an [? age ?] BuildContext safely read the media query
and give us up-to-date values?
What if the user resized the window during the await?
Good news.
In this case, Flutter will do the correct thing,
and you don't need to worry.
Now it is worth noting that this code is a pinch contrived.
Normally code like this lives in the main area of your
build method.
But if you watched the previous "Decoding Flutter" episode
about didChangeDependencies, you may be spotting an opportunity
to improve this widget's performance, just like we
talked about in that episode.
Because this code will only produce new values
when the media query changes and the media query changing
will always activate your widget's
didChangeDependencies method, you
can certainly move this code into that method
and ensure it's only ever run when it can possibly
produce new values.
And this pattern is great, because it
allows us to only perform calculations and cache
values once, precisely when our old values grow stale.
And our BuildContext is always synchronous and fresh.
Asynchronous use of BuildContexts
is one of those things that's fine until it isn't, which
is the reasoning behind the new lint.
For more info on Flutter, head to Flutter.dev.
[MUSIC PLAYING]