Do you know what that context object is? You know what object I mean, the BuildContext object named, context, that’s passed to the build() function all the time. It’s a necessary parameter to a bunch of static functions as well:
It helps you go up and or through the ‘render tree’ (or widget tree). In this article, we’ll take a closer look at it. That means, we’re going to look ‘under the hood’ of the Flutter framework and get down to what exactly makes up this BuildContext object named context. That means we’re walking through the code. In fact, let’s not keep you in suspense, I’ll tell you right now what this object is.
It’s an element.
I Like Screenshots. Click For Gists.
As always, I prefer using screenshots in my articles over gists to show concepts rather than just show code. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code in a gist or in Github. Further, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers — not on our phones. Not yet anyway.
Not In Context
As it happens, We’re not going to examine the BuildContext class itself to any great detail per se. With it seemingly being such a pivotal player in the Flutter framework, simply presenting the part it does play in a typical app should be enough. Besides, it’s an abstract class — you have to create a subclass and implement all the fields and functions it contains.
I’ll present a screenshot of the class below with all its documentation and deprecated functions removed — just to give you a hint as to its role in Flutter. You may recognize some of the functions in contains and may even be surprised that it’s in this class where these functions reside. Next, we’ll determine the precise subclass that uses BuildContext. Granted, I have already spilled the beans on that one, but act surprised anyway, ok?
The Element of Widgets
Let’s step back a bit and first look at the class, StatelessWidget. Below is a screenshot of one with all it’s documentation stripped out as well so you can see just what a StatelessWidget is made up of. Not much to it, is there? It’s an abstract class, and so a subclass must, of course, implement its build() function. We pretty much knew that already. However, what’s this createElement() function? It instantiates another class called, StatelessElement, and actually passes a reference to itself as a parameter. Let’s take a look at that class next.
For the class, StatelessElement, I decided to leave the documentation in — what little there is. Pretty short class as well. Note, it takes the StatelessWidget parameter and passes it on to its own parent class, ComponentElement, and so, we’ll press on there.
The class, ComponentElement, gets a little more involving, and so I just took a screenshot of the start of this class. Note, it too is an abstract class. Makes sense too, as it contains the very build() function that needs to be implemented. Of course, what we’re doing here is going back through the class hierarchy at the moment. Don’t worry, we’ll be re-visiting these ‘intermediary’ classes again. We now have the class, Element, to look at next.
Again, we’re looking at just the start of the class, Element, next. Good thing too. In comparison to the classes so far, this one’s huge. In all, it’s made up of 90 functions and properties. Yeah, this is indeed an important ‘element.’ However, we’ve arrived at what I was getting at. What do you see?
The class, Element, implements the class, BuildContext. Unlike Java, for example, any class in the Dart programming language can be utilized as an Interface — you merely append its name on the end of a class declaration behind the keyword, implements. Of course, unless your class’s abstract, you then have to implement all the functions and fields that make up that class. In the case of the class, Element, implementing the BuildConext class with its many members — no wonder there’s now a large number of functions and fields. With this little exercise finally over, we can conclude, as with all object-oriented languages, an object of type, Element, can also be passed to functions or constructors as an object of type, BuildContext. Surprise!!
The Flutter documentation states they decided to use the BuildContext as an Interface so to discourage direct manipulation of Element objects — whatever that means. I mean, who wants to deal with 90 class properties anyway!?
So, now you know, when you place a breakpoint in your favorite IDE right on the build() function line, you’ll discover the context parameter passed to that function is, in the case of the StatelessWidget, the very StatelessElement object we first saw instantiated back in the StatelessWidget screenshot. Note, the same is true for the StatefuleWidget. In its case, however, it involves the object, StatefulElement. Both these two types of widgets are listed below.
Remember, each widget has its own Element object (BuildContext object). An Element (BuildContext) can only belong to one widget. Again, when you were first learning Flutter, you possibly concluded that all the widgets that make up your app linked in a sort of a ‘tree-like’ fashion. They are, but not directly. It’s their corresponding Element objects, in fact, which are linked together. Each element (context) contains a reference to their ‘parent’ element as it’s the parent element (context) that actually creates the ‘child’ element when a Flutter app starts up. It could be said, a Flutter app is made up of BuildContexts objects chained together composing a ‘tree of BuildContext objects.’ They remain constant during the app’s lifecycle while their ‘widget’ counterparts may get disposed of and re-created over and over again.
Put This In Context
I’ll utilize a simple sample app from a previous article to demonstrate the ‘inflating process’ involved when starting up such an app and how each widget incorporates its element (context) counterpart. The app’s home screen along with its code is displayed below. At a glance, we have ten widgets.
The screenshots below reveal when the function, createElement, for a StatelessWidget is actually called. As it is, the widget’s corresponding element object is created in its parent element’s function, inflateWidget(). The middle screenshot displays that function and shows the newly created element object then calling its own mount() function. It's there where the parent element is assigned to the child’s instance field, _parent, further adding to the render tree. With every widget, there is its corresponding element/context object.
Go Around With Context
With this link of ‘BuildContext’ objects, you have the means to go back up ‘the tree’ to retrieve previously instantiated Widgets and State objects. All those “of “ functions, for example, listed at the beginning of this article each contain a function implemented in the Element class and defined from the BuildContext class:
The Running Theme
Let’s end this with a couple of the more popular implementations. Working with Flutter, you’ll find the static function, Theme.of(), is used repeatedly throughout the Flutter framework. The screenshot on the left-hand side below depicts a common application — retrieving the ThemeData object to supply a particular style to a Text widget. The screenshot on the right-hand side reveals the function, dependInheritWidgetOfExactType, defined in the BuildContext class is used to find a particular ‘type of InheritedWidget’ in a Map object called, _inheritedWidgets.
Find A Scaffold
The static function, Scaffold.of(), is also very common in Flutter apps. It contains another function defined in the BuildContext class and implemented in the Element class. This one goes back through the linked list of Element objects to have a previously defined State object. There’s literally a while loop involved going back through the linked list of Element objects (each has a reference to its parent) looking for a Widget of the specified type. In this case, the Scaffold defined previously back in the render tree is sought for and its associated State object, ScaffoldState, is returned.
Let’s leave it there. The Element class implements the BuildContext class, and when I have time, we’ll look to the many other functions also implemented to retrieve the many ‘elements’ of your app already instantiated and referenced as part of the render tree.
*Flutter Stable channel dated September 10, 2020