The InheritedWidget Mixin
In your app’s interface, rebuild only those widgets you want to.
Here, copy this gist, inheritedwidget_state_mixin.dart, and you’ll have a Mixin that provides you with a powerful capability by implementing an InheritedWidget to any particular State class. When you then call that State object’s setState() method, it won’t rebuild its whole interface — only selected parts of it. Thus making such rebuilds much more efficient. You don’t want a complex interface with dozens of widgets rebuilt all over again! That’s poor performance. In this article, a simple example app will be used to demonstrate this Mixin. If anything, this article will demonstrate to you the use of Mixin’s and InheritedWidgets.
Let’s begin.
Show By Example
I’ve written up and stored an example app in the Github repository, inheritedwidget_state_mixin_example. Below is a video of that example app. You’ll quickly recognize it to be the simple counter app that comes with every new Flutter project. In addition, the english_words package introduced in the codelab, Write Your First App, is also utilized for demonstration purposes, and so, a random collection of ‘word pairs’ appears in red. Finally, you’ll note the extra FloatingActionButton and counter. It’s there to further demonstrate another feature of the Mixin.
Now, in the second screenshot below, you’ll see some dotted red circles. Those are the only widgets that are being rebuilt at any given time or from user interaction. The first screenshot of the code highlights what segments are ‘rebuilt’ while leaving the rest of the code untouched.
Granted, this is a very very simple example. However, imagine a very busy screen with dozens and dozens of widgets, but just one being rebuilt with every push of that button. The less rebuilt; the better. It’s simply better runtime performance — and this is easily achieved with the mixin, InheritedWidgetStateMixin. It involves an InheritedWidget. Just apply the Mixin to a State class. See below.
A Different Mix
Now implementing this Mixin means things behave a little differently. The setState() method is still used, but now the whole interface is not rebuilt. Especially when you now present the interface in the buildIn() function and not in the traditional build() function. See the second screenshot below. Note, you can continue to override the build() function. That just means this Mixin is very likely made useless.
Inherited Built-In
The build() function is still used in this State class and is still called again with the setState() method. However, with this Mixin, the build() function now contains a built-in InheritedWidget. The first screenshot below is that of the Mixin itself.
As you see, the build() function returns the built-in InheritedWidget. Every time it’s called, the build() function now returns the InheritedWidget of the type, _InheritedWidget. Now, look carefully below at the line, _child ??= buildIn(context). That tells you the interface created in the buildIn() function is only ever created once! The interface resulting from this function now never changes — unless you explicitly tell it to. And in the second screenshot below, you see where I’m telling it to.
Let’s look at the first widget highlighted above in the variable called, wordPairsWidget. Note it’s a StatelessWidget. As such, unlike its StatefulWidget counterpart, it traditionally remains unchanged. However, that fact will soon demonstrate the InheritedWidget's unique capabilities.
In the first screenshot below, we see this Stateless widget being first initialized in the initState() method. The number of seconds between each change of a word pair is provided. Why this widget is assigned to the variable, wordPairsWidget, due to the Timer object it contains. You’ll see why shortly.
The second screenshot is the StatelessWidget itself. It reveals the Timer object being defined within. Note, that the State object has been passed to this StatelessWidget so its setState() method is then called between those 3-second intervals. The plot thickens!
What’s The Word
True, not the greatest-looking code. However, this is all to demonstrate the ‘unique’ InheritedWidget process involved. Further in this StatelessWidget is its build() function. See below.
The BuildContext’s findAncestorStateOfType() function is used to retrieve a State object of type, InheritedWidgetStateMixin. Of course, one such object happens to be this StatelessWidget’s parent in the Widget tree. The Mixin’s dependOnInheritedWidget() function is then called. The thing is, when working with InheritedWidgets, that means when the associated InheritedWidget is called again, this build() function in this StatelessWidget will be called again. That’s magic!
The second screenshot above is that of the dependOnInheritedWidget() function in the Mixin itself. You can see, that when the built-in InheritedWidget is available, its InheritedElement is passed to the BuildContext’s dependOnInheritedElement() function. That StatelessWidget is now a ‘dependent’ of an InheritedWidget.
So, when that State object’s setState() method is called again every three seconds, that State object’s build() function is called again. In turn, the InheritedWidget class is called again as you see in the second screenshot below. The Flutter framework ‘remembers’ when an InheritedWidget is called again, and so it's simply updated. However, any dependents are rebuilt again. That’s cool!
I won’t continue with the sequence of function calls that get the dependents rebuilt again as this will take us deeper into the Flutter framework itself. Instead, I want to continue reviewing the Mixin. However, further down this article, the TL;DR section goes through the process to its completion.
setState calls stateSet
The other two locations in the buildIn() function where the interface can change are enclosed in the stateSet() function.
Nice name, right?
Thought it up myself.
See the first screenshot below. The underscore is used in this case because the BuildContext parameter, context, is not needed in this particular WidgetBuilder function. It is a WidgetBuilder type that’s passed into the stateSet() function: Widget Function(BuildContext context);
The idea is you can then have a number of these stateSet() functions here and there in your interface each with one or two widgets that need to be updated all the time. You want to leave the rest of the interface alone. Conceivably, dozens of widgets can make up your interface, and it would be ‘expensive’ to update the whole interface just as often. The second screenshot below reveals the stateSet() function.
The WigetBuilder is passed on to a StatelessWidget, _DependencyWidget. You see that WidgetBuilder is then being called in that StatelessWidget’s build() function. See below. The stateSet() function essentially makes the StatelessWidget mutuable with the function, dependOnInheritedWidget.
Take A Time Out
By the way, because the WordPair widget, wordPairsWidget, is a variable, it’s easy then to cancel its timer when the app moves off the first page. In the first screenshot below, you can see the variable has a deactivate() method.
It Takes Time
In turn, the _WordPairsTimer object calls its own deactivate() method which reveals, in the second screenshot below, where the Timer object calls its cancel() method. It’s good practice to not have a Timer continue when it’s not necessary. We’re on another screen, for example, and so it’s just taking up resources. When returning to the first page, the Timer is created and initialized once again with the activate() method.
Home Inheritance
The stateSet() function can utilize any InheritedWidget available to it. This allows you to dictate which InheritedWidget controls which dependency widget is rebuilt. An example of this is the second FloatingActionButton. That counter is associated with an InheritedWidget residing in a different State object — the Home Page.
stateSet<HomePageState>(() {
The second screenshot below is of the Home Page State class. As you see, it too uses the Mixin, InheritedWidgetStateMixin. With that, its build() function is already in use (third screenshot below), and so the interface is instead defined in the buildIn() function. Note, in the second screenshot, the const keyword in front of the FirstPage StatefulWidget. That means this StatefulWidget is defined at compile-time and now will not change — no matter how many times the HomePageState’s build() function is called. However, that’s alright. It has its InhertiedWidget that you can call still using this State object’s setState() function.
The Home Page’s State object is accessed by the variable, homeState.The Home Page’s State
In A State
If the specified State object is defined and not used (first screenshot below) the StateSet() function returns essentially a StatelessWidget and so nothing will change once first. with the WidgetBuilder function’s content in its build() function. As such, in this example app, the second FloatingActionButton does not work anymore. No error is given. It’s simply not implemented properly, and that’s on you the developer.
Three Of A Kind
The little blue buttons along the bottom are labeled, ‘New Dogs’, ‘New Cats’, ‘New Birds’, and ‘New Foxes.’ Press a button, and three new images of that chosen animal are downloaded from a public API. This process involves an InheritedWidget.
As you may know, when an InheritedWidget is called, again and again, any other widgets anywhere in the app that was ‘registered as a dependency’ to this InheritedWidget are also rebuilt — as if their setState() functions were themselves called. Now, that is powerful! Distributed rebuilds of selected widgets in an interface have always been the strongest reason to use InheritedWidgets in my opinion.
It’s For The Birds
The class defined in the screenshot above is for the birds. You’ll find there are three additional classes very similar to this one — each with the Mixin, InheritedWidgetStateMixin, and so each with their own built-in InheritedWidget. Each built-in InheritedWidget is further ‘linked’ to another three State objects. These additional State objects are each responsible for displaying an image of a particular animal. For example, here, three ‘bird image’ state objects will be dependent on this ‘Bird’ Inherited State object displayed above.
When this State object above is called again to rebuild, its InheritedWidget is called again, and so its three ‘image’ State objects will be rebuilt again. This results in three new images of birds every time you press the ‘New Birds’ button. See how that works?
Note, these images are coming from the public API’s listed below:
https://shibe.online/api/birds
https://shibe.online/api/cats
https://dog.ceo/api/breeds/image/random/1
https://randomfox.ca/floof
State Your Inheritance
In this simple example app, when you press the ‘New Birds’ button, for example, only those three areas of the screen displaying birds are getting repainted. It’s not the whole screen getting rebuilt. It’s just those three areas. Now, that’s efficient!
The screenshot below is of the example app’s ‘Grid Page’ It does look a little odd with its series of cascading calls of ‘Inherit’ classes one after the other ending with a GridView widget. Note, this is just for demonstration purposes. However, it does represent a typical Futter app in a microcosm. After all, typical Flutter apps are just a series of Widgets called one after another with one calling another.
Each one of those ‘Inherit’ StatefulWidgets has its State object counterpart with the mixin, InheritedWidgetStateMixin. Now, the first thing executed in that screenshot is the expression, con.children, for the GridView’s parameter, children. This expression returns a List object containing those ‘image’ State objects mentioned earlier. Three of each type of animal for a total of twelve StatefulWidgets will be contained in that List object. Each in that List object has access to its designated public API to get the appropriate animal image. Do you follow me so far?
In fact, let’s take another look at the ‘Bird’ State object below. You can see that its buildIn() function is returning the InheritCat StatefulWidget, and so on.
The State’s Image
Let’s quickly look now at the StatefulWidgets responsible for displaying the individual images and are the very widgets placed in the List object described above. The screenshot below conveys part of the routine used to produce three separate images of a group of animals or of birds and randomly place them in the GridView widget. It is there where we’re introduced to the ‘Random’ classes.
Looking at the ‘Bird’ version of these classes, you can see it contains the API information necessary to download an image of a bird. Note, it also takes in an instance of the class, BirdController, by calling the State object’s add() function in its constructor. Keep that class in mind.
If you recall, back in at the ‘First page’, the InheritBird StatefulWidget class was being instantiated and associated with its State class, _InheritBirdState. Below is a screenshot of that State class once again. Did you notice the other class being instantiated there? One that even takes in that State object as a parameter? It’s the same class that is then later taken in by a ‘Random’ class object: add(BirdController())
. Hence, a link is made between a particular InheritedWidget and its three ‘image’ widgets.
This is possible with the use of a factory constructor in all four of these Controller objects. The BirdController uses a factory constructor and so supplies the same instance of this particular class to the corresponding ‘image’ State object, _RandomBirdState. Again, the reason to make this connection is to form a ‘dependency’ between those ‘image’ widgets and their particular InheritedWidget.
The State object, _RandomBirdState, calls the public API. More specifically, the parent class that it extends, ImageAPIStateMVC, calls the public API. See below. The ‘Inherit’ Controller taken in with the add() function is then used to make this State object dependent on a particular InheritedWidget. When that ‘Inherit ’ Controller then calls its newAnimals() function, the InheritedWidget is called again, and so this State object is then rebuilt. I know, it’s a little tough to follow here, but step through the app a few times in your favorite IDE, and you’ll come to see the process.
Highlighted above is the function, dependOnInheritedWidget(). It ‘assigns this State object as a ‘dependent’ to an InheritedWidget. You may realize such a connection traditionally involves one of Flutter’s own functions: dependOnInheritedElement() or dependInheritedWidgetOfExactType(). It still does, but one’s now called by the Mixin. See below.
Another video below again demonstrates what happens when you press those buttons along the bottom of the screen. A screenshot of those buttons reveals a Controller object calls the appropriate function to update only small selected areas of the screen.
The HomeController is the class object that contains those ‘new’ functions you see highlighted above. It’s displayed in the first screenshot below. Notice those ‘Inherit’ Controller objects with their factory constructors are being utilized again. This time, they’re calling their ‘new animals’ functions. The second screenshot below contains their parent class, InheritController. Notice, the newAnimals() function calls something you’re familiar with by now, the setState() function. It’s calling a State object to rebuild. However, which State object is the question when using this Mixin.
Well, that question is quickly answered with one last look at the screenshot of the State class, _InheritBirdState. Remember, the ‘Inherit’ Controller, BirdController, takes in that State object, and so now calling setState() in that Controller will call this State object’s setState() function. In turn, the build() function in the Mixin will be called. See the second screenshot below.
Cheers.
TL;DR
Let’s peek under the hood and see how this InheritedWidget dependency magic is implemented in the Flutter Framework. You should know how this all works.
In the first screenshot below, we have the Mixin’s dependOnInheritedWidget() function again. In that function, we see the dependOnInheritedElement() function being called because the variable, context, is of type, Element. That function is defined in the Element class.
What we’re seeing at that moment, in this example app anyway, is the StatelessWidget, wordPairsWidget, represented by its Element object, context, being assigned as a dependent to the built-in InheritedWidget which, in its own right, is being represented by the InheritedElement object, _inheritedElement. Follow so far?
In the second screenshot above, you can see, through the use of their corresponding ‘Element’ objects, how the StatelessWidget, wordPairsWidget, and the built-in InheritedWidget record the dependency between each other. When the InheritedWidget’s own ‘Element’ object calls its updateDependencies() function, eventually its Map object, _dependents, will take in the StatelessWidget’s element, context, thus designating it as a dependent. When the InheritedWidget is then called again, all the widgets associated with all the elements within that Map object will be rebuilt (their build() functions called again). Still with me? I’ll show you.
When a StatefulWidget calls its build() function again, the child widgets first created in that the build() function are simply updated again. More specifically, the child widget’s Element objects are updated again.
In the first screenshot below, the updated() function for the InheritedElement object directs its InheritedWidget to call the updateShouldNotify() function. You’re free to override that function, by the way, and dictate whether the process is to continue. If that function returns true, the process continues, and eventually the InheritedElement’s notifyClients() function is called. See the second screenshot below.
In the notifyClients() function, Flutter now iterates through that Map object, _dependents, that contains a ‘dependent’ Widgets Element object (context). The screenshots below depict the next few functions that are then called. This will eventually lead to the appropriate Widgets to call their build() functions again. Stay with me.
In the first screenshot above, know that each Widget has its function, didChangeDependencies, called in turn which calls their Element object’s markNeedsBuild() function — the very same function called whenever a State object calls setState(). See below. All this will finally result with those ‘dependent’ Widgets calling their build() functions again.
There, that was easy.
Top comments (0)