Decode InheritedWidget

An in-depth look at the InheritedWidget

This sample app, counter_app_inherited, is being used in this article to showcase the InheritedWidget. It supplies the simple ‘counter app’ you’ll no doubt recognize whenever you create a new Flutter project. However, in this app, an InheritedWidget is involved in incrementing the counter.

Looking at the screenshot below, the red arrows highlight how the InheritedWidget is being implemented. As always when the ‘floating button’ is pressed, a setState() function is called. However, in this app, it’s a static setState() function defined in the class, BuildInheritedWidget. The traditional expression, _counter++;, is nowhere to be seen, and yet the app does increment successfully. How does that work? Know now that it is in the widget, BuildInheritedWidget, where you’ll find the InheritedWidget, and as you see below, that widget also wraps around the CounterPage widget. Being such a simple example app, this all may look a little strange, but it’s so to truly showcase the InheritedWidget’s capabilities. You’ll see shortly.

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.

Let’s begin.

Image for post
Image for post
Other Stories by Greg Perry

Let’s quickly take a look at that BuildInheritedWidget widget. It’s not an InheritedWidget, but another StatefulWidget. Again, because this is such a simple example app, this widget is needed so to demonstrate what I would say is the most powerful ability of the InheritedWidget.

As you see in the screenshot below, the InheritedWidget is indeed instantiated here with the BuildInheritedWidget widget. It itself is called, InheritedData, and we’ll look at that next. For now, notice that this InheritedWidget is instantiated in a State object’s build() function and merely passes along the parameter from the BuildInheritedWidget widget, widget.child, to the InheritedWidget’s constructor. Note, this ‘child’ object is the portion of the widget tree already instantiated (CounterPage) and again is simply passed along.

You see, instantiating this InhertedWidget in what is essentially a standalone StatefulWidget (BuildInheritedWidget) is done so we can call only its State object’s setState() function. This way, this InheritedWidget (and consequently any of its ‘dependent’ widgets) will only be rebuilt instead of rebuilding the app’s entire widget tree. You’ll see what I mean soon enough.

Further note, that State object is itself instantiated in a separate static field called, state. This approach allows the app to again only call that particular setState() function, thus only calling this State object’s build() function which contains the Inherited widget, InheritedData. Follow me so far? Don’t worry, I’ll explain further.

Image for post
Image for post
app_counter_inherited.dart

Inherit The Widget

Looking now at the InheritedWidget class, InheritedData, we see this immutable class has a mutable instance field called, object, of type, DataField. Simply put, the instance field, data, in the class, DataField, is this app’s counter. As for the InheritedWidget, InheritedData, you’ll find the magic happens in its updateShouldNotify() function. You see, when an InheritedWidget is instantiated again (i.e. called again), it calls its updateShouldNotify() function passing in as a parameter its ‘previous’ instance (oldWidget). This sample app takes advantage of that fact.

Image for post
Image for post
app_counter_inherited.dart

Accumulate The Count

As you see in the updateShouldNotify() function above, the current ‘counter’ (the field, data, in the class, DataField ) is assigned the previous counter value and then adds one more integer. Hence, you have your incrementation. Further note, the getter, data, listed above is used to display the current count.

The screenshot below depicts a breakpoint pausing the app’s execution to reveal the current ‘DataField’ object always starts with zero, but it is soon assigned the previous object‘s data value plus one. In this case, accumulating the count up to, two. Take a look at the ‘variables’ on the right-hand side below.

Notify The Inheritance

Now, the observant reader will have noticed the updateShouldNotify() function displayed in the InheritedWidget above, which returns a boolean value, has not one but two return statements?? You can see it again below. Now, the second return statement is considered ‘dead code’ and so there’s no complaint by the compiler, but it’s there for a very good reason. Comment out the first return statement, and you’ll quickly see the power of the InheritedWidget.

To put it plainly, a value of True will ‘mark for rebuild’ any and all widgets that have ‘accessed’ that InterheritedWidget during the course of the app’s lifecycle. It will make those widgets call their build() functions again. In contrast, a value of False, will not notify these ‘dependent widgets’, as they call them, and so unless otherwise, will not be rebuilt.

Image for post
Image for post
app_counter_inherited.dart

The importance of that fact can be readily demonstrated because if you do comment out that first return statement, hot reload, and then press the floating button — the counter won’t increment. Go ahead, try it for yourself.

Placement Is Key

By the way, below is a screenshot of merely wrapping the simple example app with the InheritedWidget itself. I’m finding a lot of developers are doing this with their own apps, and dare I say, it’s not a stellar move. Simply placing the InhertiedWidget directly above what makes up the rest of the app’s widget tree will not take full advantage of the InheritedWidget. With every rebuild of the InheritedWidget, because of its placement, the rest of the app’s widget tree will simply rebuild regardless of the boolean value in the updateShouldNotify() function. Not good.

Image for post
Image for post
app_counter_inherited.dart

Inherit The State’s Element

Let’s step back a bit and look at the class, StatelessWidget, for a moment. Be aware, that every StatelessWidget and StatefulWidget has a corresponding Element object of an appropriate type also instantiated whenever such widgets are first created. It is these Element counterparts that are ‘mounted’ and, in fact, represent the widget tree. In the case of a StatelessWidget, it has the class called, StatelessElement. As you see below, when instantiated, the Element object takes in the instance of its associated Widget.

Image for post
Image for post
StatelessWidget in framework.dart

Deep in the Flutter framework when your widget is being ‘inflated’ (attached to the widget tree), it’s createElement() function you see above is called. The resulting Element object then calls its mount() function. We’re on a bit of a tangent here, I know, but please stay with me.

Image for post
Image for post
Element in framework.dart

The Tree On The Mount

The mount() function in Flutter is responsible for forming what is called the ‘widget tree.’ Your widgets are linked together through their Element counterparts. Highlighted by the first arrow below, we see where that StatelessWidget is indirectly ‘linked’ to its parent widget through their Element counterparts. The current Element’s instance field, _parent, is assigned the ‘parent’ Element object.

Image for post
Image for post
Element in framework.dart

Update Your Inheritance

Note the second red arrow on the screenshot above. This is an interesting function and is relevant to this article. A screenshot of that function is displayed below. Note, the parent Element’s Map object, _inheritedWidgets, is being assigned to the new Element object’s own Map object! Every widget you make has this Map object in its Element counterpart, and by design, it collects all the ‘InheritedWidget Elements’ conceived in your app up to that point so far. This happens every time with every new widget.

Image for post
Image for post
Element in framework.dart

What’s more, each Element object created with your Widgets has the following instance fields listed below. The first two represent the collection of ‘InheritedWidget’ Elements objects involved in the Flutter framework and, of course, in your own particular Flutter app up to that point so far. Note, the Element type used with the InheritedWidget is called, InheritedElement.

Image for post
Image for post
Element in framework

Now, take a look at the InheritedWidget class used in this sample app. Of course, it extends (or shall I say, inherits) the class, InheritedWidget. Again as such, its Element counterpart is the class, InheritedElement. In the screenshot below, the createElement() function was overridden and merely duplicated again to display that Element class just for demonstration purposes. It too takes in the instance of the Widget itself as a parameter.

Image for post
Image for post
app_counter_inherited.dart

Again, the mount() function is linking the InheritedElement to its parent Element object, and again, the _updateInheritance() function is called.

Image for post
Image for post
Element in framework.dart

However, in the InheritedElement class, the _updateInheritance() function has been overridden as well. This time not by me, but by the Flutter team. It still passes on a copy of its parent’s Map object containing all the app’s ‘InheritedWidget’ Elements up to that point, but it now also adds itself to this Map object using its widget counterpart (more specifically the ‘type’ of its widget counterpart) as the key. Very, very interesting!

All your other widgets that are created from now on will have an indirect reference to that ‘inherited’ widget in their own Element’s Map object of ‘InheritedWidget Elements.’ By the way, that is a very important phrase, ‘From now on...’ Your widgets created and ‘attached to the widget tree’ before the InheritedWidget, InheritedData, will not have it listed in their own ‘InheritedWidget Element’ Map objects. Only descendants of the widget tree at that point can now gain access to this InheritedWidget.

Image for post
Image for post
InheritedElement in framework.dart

Find Your Inheritance

And so, by the time the build() function in the class, CounterPage, is executed, the InhertiedWidget, InheritedData, is now recorded in a bunch of Map objects — each map in each Element object associated with every widget inflated (attached to the widget tree) ‘after’ this InheritedWidget was mounted and attached to the widget tree.

The CounterPage widget in the sample app is wrapped, as it were, inside the InheritedData widget. Though, as a class, CounterPage is instantiated before the InheritedData class object, it is ‘inflated’ after the InheritedData widget. With that, the ‘of function highlighted by the first arrow below will consistently find that ‘InheritedData’ object, and the getter, data, will consistently display the current count. See how that all works now? To further help you understand all this, let’s take a look at the static function, of, in the class, InheritedData, in the screenshot after this one.

Image for post
Image for post
app_counter_inherited.dart

It’s All Context

See that ‘context’ parameter in the build() function above? In this case here, that’s the Element object counterpart for the CounterPage widget! The static function, of, allows for the Element object, context, (of type StatefulElement by the way) to call its dependOnInheritedWidgetOfExactType() function specifying the type, InheritedData, in between the function’s parameterized brackets. This is so to find the Element counterpart (of type InheritedElelment by the way) for the widget, InheritedData. See how this works now? Is it all coming together now?

Image for post
Image for post
app_counter_inherited.dart

Depend On Inheritance

Let’s continue further into the Flutter framework, and now examine the dependOnInheritedWidgetOfExacttype() function. Again, the CounterPage’s StatefulElement object was ‘attached to the tree’ (or mounted) after that of the InheritedData’s InheritedElement object, and so the expression, _inheritedWidget[T], will be successful and return that InheritedElement object. Further note, the first red arrow below highlights that the dependOnInheritedWidgetOfExactType() function does indeed confirm the parameterized generic type between the brackets extends the type, InheritedWidget. Of course, the class, InheritedData, does just that.

Note the function’s name starts with, ‘dependOn.’ The ‘depend on’ aspect of all this ‘inherited business’ will now be played out in the next function dependOnInheritedElement() highlighted by the last arrow below.

Image for post
Image for post
Element in framework.dart

Again, each Element object created with your Widgets has the instance fields listed below. The dependOnInheritedElement() function is now going to involve the Set object highlighted below called, _dependencies.

Image for post
Image for post
Element in framework

A screenshot of the dependOnInheritedElement() function below reveals a very important operation that occurs almost every time you use an InheritedWidget’s of function. Let’s walk through it and see what it does. If not already, the Set instance field, _dependencies, is initialized. The InheritedElement object found in that Element’s Map object is then added to that Set object.

In the case of the CounterPage widget, the InheritedData’s InheritedElement found in the StatefulElement’s Map object is now added to the StatefulElement’s Set object. Got that so far? Next, you see the InheritedData’s InheritedElement passes as a parameter the CounterPage’s StatefulElement object to the updateDependencies() function — adding that StatefulElement object to its own Map object called, _dependents (see next two screenshots below). Finally, this function (remember, originally called by the of function) returns the Element’s widget counterpart using, ancestor.widget. Of course, in this case, it’s the widget, InheritedData. Still with me?

Image for post
Image for post
Element in framework.dart
Image for post
Image for post
InheritedElement in framework.dart
Image for post
Image for post
InheritedElement in framework.dart

So what’s all this collecting of Element objects into Map and Set objects? Well, it involves that boolean value returned by the updateShouldNotify(). We’ll now walk through the process involved when that function returns True.

A Pressing Situation

Let’s visually track the series of events that occurs when you press that ‘floating’ button. Pressing the button will call the State object’s setState() function which results (as depicted in the first screenshot below) in calling the static setState() function. This results, in turn, with the State object, _BuildInheritedWidgetState, calling its build() function again and instantiating the InheritedWidget, InheritedData, again. It doesn’t go through the whole app’s widget tree, just that small little build() function.

Now, because the class, InheritedData, is an InheritedWidget, its updateShouldNotify() function will be called with this rebuild. Notice this function is in the second screenshot, and the returned boolean value is True. Now, if it were false, the execution will stop right there. However, it’s True and so the build() function in the State object, _CounterPageState, is eventually called. Note, in any other circumstance, the State object, _CounterPageState, would never be called again. Ever. You see, with the intervention of the StatefulWidget, _BuildInheritedWidgetState, it’s normally called once when the app is first started up, and the counter would always be zero.

In the third screenshot above in the top row, we see the CounterPage build() function is called displaying the now incremented count supplied by the expression, var data = InheritedData.of(context).data;. The call to this build() function came about because of two things. First, it accessed that InheritedWidget with that of function, and second, that InheritedWidget’s updateShouldNotify() function returned True. That’s the only way with the way this app is now organized.

Remember the following happens every time you call the function, InheritedData.of(context): The dependOnInheritedWidgetOfExactType() function, with the type, InheritedData, in between its parameterized brackets, will consistently find the Element counterpart for that widget using the expression, _inheritedWidget[T]. It then records its dependency to that widget in its Set object, and finally returns the very widget, InheritedData, with the expression, ancestor.widget. This happens every time you call the function, InheritedData.of(context). Every time.

If It Were True

Now, there’s an extensive process involved if the boolean value returned by the updateShouldNotify() function is True, and we’re now going to walk through it now. Look at the screenshot below and see where the updateShouldNotify() function is called. Again, it’s called in the InheritedElement object — when its Widget counterpart is rebuilt. As you see below, if it returns True, the parent’s function, super.updated(oldWidget), is then called. Let’s see what happens next.

Image for post
Image for post
InheritedElement in framework.dart

In the parent class, ProxyElement, the updated() function, in turn, calls its notifyClients() function. The ProxyElement class is an abstract class and its notifyClients() function has to be implemented. As it happens, it is implemented back in its child class, InheritedElement.

Image for post
Image for post
ProxyElement in framework.dart

Back in the InheritedElement object, we see now the Map object, _dependents, comes into play. The notifyClients() function goes through all the ‘dependents’ (collected by any calls of the ‘offunction) testing that they are indeed dependents using the Set object, _dependencies. Finally, that ‘dependent’ element calls its own didChangeDependencies() function in its own notfiyDependent() function. See next screenshot below.

Image for post
Image for post
InheritedElement in framework.dart
Image for post
Image for post
InheritedElement in framework.dart

It’s here in the Element object’s didChangeDependencies() function where it finally calls its markNeedsBuild() function ‘marking’ its widget counterpart to be rebuilt. That’s huge! Again, any and all widgets that had called the InheritedData.of(context) function will be ‘rebuilt’ whenever the InheritedData widget is ‘rebuilt.’ Huge.

This allows for spontaneous and sporadic ‘rebuilds’ throughout an app. It’s possible to do so without rebuilding the app’s entire widget tree. That alone encourages optimum performance. Heck! Who cares about accessing information using an InheritedWidget from up the widget tree. Collecting all those ‘dependent’ elements into that _dependents Map object is the real power of the InheritedWidget! Don’t you agree?

Image for post
Image for post
Element in framework.dart

And so, the big takeaway from this article is the expression, InheritedData.of(context). At that instant, the InheritedWidget has now recorded as a ‘dependent’ the very widget that called that static function. I mean, ‘going up the widget tree’ to retrieve something is alright I guess, but allowing for spontaneous ‘rebuilds’ here and there throughout your app without rebuilding the app’s whole widget tree? Now that! That is powerful!

Cheers.

→ Other Stories by Greg Perry

Image for post
Image for post
DECODE Flutter on YouTube

Written by

Freelance Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store