In the last episode, you implemented an inherited widget. You saw how it easy it was to access the widget in the widget tree and read from it. But unfortunately, you can’t change a value. Inherited widget’s are immutable. Mind you, can access objects in an inherited that can mutate their own internal state, but you can’t change that object.
Inherited widgets also don’t change state. They do contain a method called, updateShouldNotify that determines whether a widget subtree will rebuilt in the case that the inherited widget is changed. That is, recreated in the tree like Stateless Widget. So this begs the question, how do you manage mutable state with inherited widgets. For this, you need to employ stateful widgets.
By combining stateful widgets with inherited widgets, you are able to maintain state and allow child widgets to rebuild the inherited widget due to state changes.
Here’s how it will work. We create a stateful widget and put all of our state in the State class like any regular stateful widget. We put our state in the State class because it persists through widget tree rebuilds.
When we add our stateful widget to the widget tree, the build method adds an inherited widget as a child.
The inherited widget then allows for any child widget to access it with ease using the of static method.
But yet, the inherited widget doesn’t contain the actual state.
This means we need to create a property on the inherited widget, that points to our state class, exposing our state to any calling code.
When we access our state down the tree, here’s what it looks like. The calling code, accesses the inherited widget, which then access the state class which finally accesses our model class. It’s indirections all the way down. Or, in this case, all the way up.
Now when I say up, in our current situation, that’s the top of the widget tree but you can use multiple inherited widgets on different branches to override state behavior. We won’t be using them in this case, but that option is available to you. Let’s put this to work.
To get started, open your project in progress or download the starter project for this episode.
In your state folder, open up pillar_widget.dart. First, we’re going to change our inherited widget. We’ll start with a name change just to make it the type clear.
Change PillarWidget to PillarInheritedWidget. Normally, you’d do a refactor rename, but in this case, we’ll use the compile errors as code markers.
We will leave the rest of this widget unchanged for now. We’ll return back in a moment. Underneath the inherited widget, type st to create a stateful widget. Call it PillarStatefulWidget.
This creates a private state class called _PillarStatefulWdigetState.
Right click and choose refactor. Rename it to PillarState.
Notice it’s no longer a private state. That’s because we’ll be sharing between various objects.
First, update the PillarStatefulWidget to the following.
class PillarStatefulWidget extends StatefulWidget {
const PillarStatefulWidget(
{required this.pillarData, required this.child, Key? key})
: super(key: key);
final Widget child;
final Pillar pillarData;
This takes in our pillarData and a child. The child represents the subtree underneath the widget. Now to the state object. Now could directly expose our model object but doing so means, the callee could change it directly. We want to be notified of changes to update the state. For that, we’ll manually our model. First, let’s expose our article count and the pillar image. Add the following:
get articleCount => widget.pillarData.articleCount;
get imageName => widget.pillarData.type.imageName;
Next, we want to increment the total articles. We’ll create a custom method for that. Add the following:
void increaseArticleCount({ int by = 1 }) {
setState(() {
widget.pillarData.increaseArticleCount(by: by);
});
}
Now when the callee increases the article count, set state is called which triggers a rebuild. Before we touch the build method, let’s return back to our inherited widget. First, let’s replace the pillarData property with a property to our state class.
final PillarState state;
Next, let’s add a property for our article count.
final int articleCount;
We use the article count to determine whether a rebuild should occur. You’ll see this momentarily. First, update the constructor to accept these new values.
const PillarInheritedWidget(
{required this.articleCount,
required this.state,
super.key,
required super.child});
Next comes our of method. The callee uses this fetch the inherited widget from lower in the tree. But remember, the callee wants in interested in the state. Instead of returning the inherited widget, we’ll return the state.
Update it to the following:
static PillarState of(BuildContext context) {
final PillarInheritedWidget? result =
context.dependOnInheritedWidgetOfExactType<PillarInheritedWidget>();
assert(result != null, 'No PillarWidget found in context');
return result!.state;
}
Instead of returning the widget, we’re returning the state. That way, they can access the state data. The inherited widget is just acting like a bridge.
Now to the should notify. This indicates whether the child tree should rebuild. Returning true means, the tree will rebuild and false means it will stay the same. We want to rebuild it only when it changes and we’re using our article counter as a marker. Update the method to the following:
bool updateShouldNotify(PillarInheritedWidget oldWidget) {
return !(oldWidget.articleCount == articleCount);
}
If the old widget contains a different article amount to the new widget, then the rebuild should proceed. Otherwise, there should be no rebuild. Okay, let’s return back to our state. Let’s write the build method:
Widget build(BuildContext context) {
return PillarInheritedWidget(
articleCount: widget.pillarData.articleCount,
state: this,
child: widget.child,
);
}
Here we pass in the article count and the current state. We all pass in the widget tree. The inherited widget will add the tree to it. And believe it or not, that’s our state management solution. Okay, open up main dart. Update the widget tree.
body: PillarStatefulWidget(
pillarData: pillarData,
child: const TutorialsPage(),
),
Now open tutorial page. Update the pillar getter.
final pillar = PillarInheritedWidget.of(context);
Then change the total tutorials.
'Total Tutorials: ${pillar.articleCount}',
Finally, open the tutorial_widget.dart and update the getter.
final pillar = PillarInheritedWidget.of(context);
And then update the call sites. Start with the Inkwell.
InkWell(
onTap: () {
pillar.increaseArticleCount();
},
child: Image.asset('assets/images/${pillar.imageName}',
width: 110, height: 110),
),
Then the CircleAvatar.
CircleAvatar(
backgroundColor: Colors.blue,
child: Text(pillar.articleCount.toString()),
And that’s it. Build and run. Tap the pillar image. Look at that, you now have a working state management solution. Using all the built in state management tools. Nice job.