When you develop Flutter applications, you barely get around seeing
BuildContexts somewhere in the code. You might intuitively know how to use it, but can you explain its purpose and its meaning in detail? This article aims to clarify this Flutter basic knowledge that seems to confuse not only beginners.
Why is the BuildContext so relevant?
BuildContext appears in a lot of places in the code of Flutter projects:
- Inside of every
build()method of a Widget as the only argument
- Inside of
Scaffold.of()and several other static functions as the only argument
- Inside of many
builderfunctions (e. g. the
It seems to always appear whenever something is happening with widgets. This is not very surprising since a widget is a very crucial concept within the Flutter ecosystem, almost everything has to do with. However, it gives us a first clue about its purpose but also about its significance.
It also let’s us know about the relevance: when it is connected to such an essential part of Flutter, a deeper understanding about its principles is needed.
What is the connection between BuildContext and Widgets?
Okay, so far we have only figured out about the
BuildContext appearing in many places inside the codes. But what does it do?
Before we clarify this, let’s remind ourselves what widgets are and how they are interconnected. For this purpose we consider a sample app with this screen:
This is the widget tree describing the visual content in a hierarchical manner as a diagram:
A widget is an immutable configuration of the visual representation of rendered elements.
If it’s only a configuration you might ask yourself: how does a widget know about its position inside this widget tree?
This is where
BuildContexts come into play:
BuildContexts are handles to the location of a widget in the tree. As a consequence, every widget has its own
BuildContext as the location inside the widget tree is unique. Furthermore, if a widget has children, parent widget’s context becomes the parent context for the contexts of the child widgets.
It’s worth noting that a widget is only visible to its corresponding
BuildContext or the
BuildContext of its parents. That makes it easy to locate a parent widget from a child widget.
What exactly do Scaffold.of(…), Theme.of(…), Navigator.of(…) etc. do?
There are numerous classes that provide a static method called
of whose only argument is a
BuildContext. Examples are
All these methods work in a similar fashion. Even their official documentation looks pretty much the same:
“The xxx from the closest instance of this class that encloses the given context”
Where xxx is a placeholder for whatever it is you are calling
of on. In
state. But what exactly does this mean?
In the case of the above widget tree, if we called
Scaffold.of() inside the
AppBar widget, the method would climb up the widget tree one step and return a
ScaffoldState representing the
Scaffold in this very location of the tree. If we found ourselves inside the
ListTile widget, it would take two steps up. The point is: it looks for the nearest widget (where distance is defined in the context of a tree as the number of edges the algorithm has to visit) of the given type and returns it.
What would happen if there was no context?
If there was no
BuildContext, it would be pretty cumbersome to pass data down the widget tree. You would be unable to use methods like
Scaffold.of(...) meaning that you would have to pass the reference to this widget down the widget tree manually to every constructor. Widget trees build up pretty quickly and a depth of more than 10 is very common. Just imagine the necessary boilerplate code for the sole purpose of making a
Scaffold widget available in the grand-grand-….-grand-child-widget.
Technically, most of the implementations of the
of methods internally call dependOnInheritedWidgetOfExactType, which is a method being defined on the
Concepts like InheritedWidget also rely on this specific part of the framework. Most of the state management solutions build upon that. So there would be hardly an state management solution apart from managing state via manual injection and callback functions.
Why is the BuildContext available everywhere inside a StatefulWidget but not inside a StatelessWidget?
You might have noticed that the
BuildContext is directly usable in every method of
State class when using
StatefulWidget. However, if you are using a
StatelessWidget, this is not the case. You have to pass it down the tree you are building.
That’s because a
StatelessWidget is immutable. Every member variable of the class must be final and thus can not be altered after the object has been constructed. The
BuildContext, however, is known later, when the
build() method is called.
Use BuildContext in initState()
When you try to use the
BuildContext in the
initState() function of a
StatefulWidget, you might stumble across some issues. The documents say:
You cannot use BuildContext.dependOnInheritedWidgetOfExactType from this method.
This means if you are trying to access the part of the widget tree above the current widget inside
initState() e. g. if you use the
Provider package or other state management solutions, this will not work.
They also propose a possible solution:
So depending on what you want to do, you might want to move your code into
didChangeDependencies(). Be aware, though, that other than
didChangeDependencies() might be called more than once during the lifecycle of the widget.
Another option if you really want to stick to
initState() is to use the SchedulerBinding Mixin:
Can we create a BuildContext?
Okay, now that we clarified the relevance of
BuildContexts the question that could pop up: (how) can we create it?
BuildContext is just a usual Dart class. However, this class is marked
abstract which means that there is no way to directly create an instance of (instantiate a)
BuildContext by a constructor call. Dart does not have the syntactic concept of interfaces like other languages (e. g. Java, C#, …). Instead, abstract classes are in use, which makes
BuildContext work as an interface.
This interface is implemented by Elements. This way, the developers are prevented from directly accessing
Elements. Instead, they work with the interface. If every
BuildContext, we can say: every
Element is a
BuildContext which might sound counter-intuitive when reading it the first time.
Whenever a widget is inserted into the tree, the
build method of the widget is called (by the framework) with the respective
BuildContext (in most cases an
Element) as the only argument.
What is an element?
Now I have introduced this new term without actually explaining it:
Element. Let me make up for this.
If you inspect the code of a widget, you will not find anything related to drawing pixels on the screen. So like I said above: a widget is nothing else but a configuration for the visual representation of the elements on the screen. Like a blueprint for what’s actually displayed.
That’s also why a high frame rate is not a big deal for Flutter because a widget is (re)created very quickly since it’s a lightweight container. The heavy lifting is done somewhere else.
To a certain degree it’s similar to the virtual DOM in web frameworks like React where actual DOM manipulations only happen when there was really a change that affects the visuals.
In Flutter it works this way: during the initial creation of a widget, an
Element is created alongside (we say: the widget is inflated to an
Element) and inserted into the element tree. Meaning there is a widget tree and an element tree. The widget tree contains all configurations and the element tree the rendered widgets. Whenever the widget changes, the framework compares the old widget do the new one and updates the element respectively. While the widget is rebuilt, the element is only updated (as a complete rebuild would require a lot of computational power).
But to make things clear: actual painting does also not happen here. This is what happens in a RenderObject.
What is a RenderObject?
This article is about
BuildContexts and not about
RenderObjects but for the sake of completion I will say a few words about
RenderObjects as they are only one abstraction level below
RenderObjects are what the visual pieces on the screen correspond to. Their purpose is to define areas on the screen regarding their spatial dimensions. They are referenced by the element. As a consequence we are dealing with another (third) tree here: the
RenderObjects together form a tree which is called
Render Tree whose root node is a RenderView (being a variant of a
The above explanations can be visually represented like this diagram:
The widgets are what we developers see in the code: properly named parts of our UI. They are referenced by the elements which they create once they are inflated. The elements also reference the
RenderObject which hold information about the visuals and contain drawing logic.
Element sort of bridge the gap between widgets (the structural part that configures the UI) and
RenderObjects (the visual part).
What to consider when working with
Depending on how strict your linter is configured, you might stumble across the warning: use_build_context_synchronously. This happens if you do something like this:
The problem here is that there is an
async operation going on (
async always means that you don’t know in advance how long it is going to take or even if it ever finishes.
The line below that where the very same context is used again, is problematic because if there is an undefined amount of time between the first call where the
context is used and the second one, how can you be sure the
context does even still exist? After all, there are many changes per second in the
Element Tree. If the
context is for example a
BottomSheet, the sheet could be long dismissed which effectively removes it from the tree.
The solution is not to store the
context but the class the
context is bound to. In this case: the
NavigatorState being return from
Navigator.of(context) (as described above):
By keeping the reference to the
NavigatorState we ensure that the
context is kept in memory across the call to the
BuildContext can barely be explained in one sentence. Although it mostly just appears as an argument of the
build method, the underlying mechanics are a lot more complex.
BuildContext is an interface which is implement by
Element, the base class for a variety of different
Element types which are being created when a widget inflates.
Element itself connects the widget (which is just an immutable configuration) to the
RenderObject that is responsible for the actual painting on the screen.
This leads to three trees: Widget Tree, Element Tree and Render Tree.
Semantically speaking, the
BuildContext is the handle to the location of a widget in the tree. It can be used to find widgets of a certain type in the tree and is the requirement for most of the state management solutions.
The reason for this separation is driven by performance needs: recreating a widget is done in no time, as it’s only a configuration object. Repainting on the other hand can significantly influence the FPS. That’s why there is the intermediate layer in form of
Elements that decides whether a repainting of the
RenderObject is necessary based upon the changes to the old version of the