Conceptualizing Synchronous BuildContexts in Flutter

Leslie Gyamfi - Jul 16 '22 - - Dev Community

If you use the Flutter lint package, which as of its 2.0 release, added the use of BuildContexts synchronous rule, you may be seeing some new red squigglies in your dart code. But does this mean use BuildContexts synchronously? If you include this rule to your linting setup either by addition of a dependency to your Flutter lint 2.0 or by specifying it yourself, your editor will start complaining about situations like this below

Image description

Flutter knows that BuildContexts are unreliable after an asynchronous gap and wants the user also to know it. The first priority in better understanding this is to have a grasp of the concept at hand. Remember that the BuildContext parsed to a widget is that widget’s corresponding element in the element tree.

Image description

Also, remember that while lots of our application code is asynchronous, Flutter’s build process is fully synchronous. There are no awaits, no futures anywhere in the assembly of your widget tree. And finally, record the unique relationship between elements and their widgets.

As flutter developers, we mostly write widgets which makes it pretty 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.

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. Flutter keeping elements around where possible makes asynchronous use of BuildContexts very unpredictable. There are many different scenarios that can make Flutter decide that so of your elements need to be refreshed from scrolling to navigation, to dragging and dropping. It’s not worth trying to memorize them all. For our activities today, let’s use the following rule of thumb: the more dramatically your widget tree changes from one frame to the next, the more 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. When this lint arrived, you probably would have found some places in your code where you were breaking this rule and probably your app wasn’t catching fire. What do you think is 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.

Image description

Image description

Consider this situation above, where a button’s callback submits a form and then pops the current page. The new lint rule will say, that 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 core of the problem at hand, 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 parsing stale BuildContexts to those methods can crash your application.

Image description
1

Image description
2

Back to that lint. The recommended fix is to do (2). 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 the widget is no longer mounted, something else may have already removed this package. But does this always work?

Image description

Imagine this scenario above, where instead of removing the page, you want to show a restaurant 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 seek confirmation of their action. In that case, do not 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. Like this:

Image description

First and foremost, we know that if somehow the user has navigated away from this screen or scrolled past it’s 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 do not care about updating any measurements for its children. So it’s safe to use the mounted check.

Image description

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? Great news, isn’t it? In this case, Flutter will do the right thing and you wouldn’t need to worry.

It is worth noting that asynchronous use of BuildContexts is one of those things that’s almost well until something happens and it is not, which is probably the reason for the new lint.

. . . . . . . . . . . . . . . . . . .