[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]
Such a nasty little edge case. Great that the compiler can catch it now!