The problem comes from your quote, which is incorrect.
As you said, InheritedWidgets are, like other widgets, immutable. Therefore they don't update. They are created anew.
The thing is: InheritedWidget is just a simple widget that does nothing but holding data. It doesn't have any logic of update or whatsoever.
But, like any other widgets, it's associated with an Element
.
And guess what? This thing is mutable and flutter will reuse it whenever possible!
The corrected quote would be :
InheritedWidget, when referenced in this way, will cause the consumer to rebuild when InheritedWidget associated to an InheritedElement changes.
There's a great talk about how widgets/elements/renderbox are pluged together.
But in short, they are like this (left is your typical widget, middle is 'elements', and right are 'render boxes') :
The thing is: When you instantiate a new widget; flutter will compare it to the old one. Reuse it's "Element", which points to a RenderBox. And mutate the RenderBox properties.
Okey, but how does this answer my question ?
When instantiating an InheritedWidget, and then calling context.inheritedWidgetOfExactType
(or MyClass.of
which is basically the same) ; what's implied is that it will listen to the Element
associated with your InheritedWidget
. And whenever that Element
gets a new widget, it will force the refresh of any widgets that called the previous method.
In short, when you replace an existing InheritedWidget
with a brand new one; flutter will see that it changed. And will notify the bound widgets of a potential modification.
If you understood everything, you should have already guessed the solution :
Wrap your InheritedWidget
inside a StatefulWidget
that will create a brand new InheritedWidget
whenever something changed!
The end result in the actual code would be :
class MyInherited extends StatefulWidget {
static MyInheritedData of(BuildContext context) =>
context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;
const MyInherited({Key key, this.child}) : super(key: key);
final Widget child;
@override
_MyInheritedState createState() => _MyInheritedState();
}
class _MyInheritedState extends State<MyInherited> {
String myField;
void onMyFieldChange(String newValue) {
setState(() {
myField = newValue;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedData(
myField: myField,
onMyFieldChange: onMyFieldChange,
child: widget.child,
);
}
}
class MyInheritedData extends InheritedWidget {
final String myField;
final ValueChanged<String> onMyFieldChange;
MyInheritedData({
Key key,
this.myField,
this.onMyFieldChange,
Widget child,
}) : super(key: key, child: child);
static MyInheritedData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedData>();
}
@override
bool updateShouldNotify(MyInheritedData oldWidget) {
return oldWidget.myField != myField ||
oldWidget.onMyFieldChange != onMyFieldChange;
}
}
But wouldn't creating a new InheritedWidget rebuild the whole tree ?
No, it won't necessarily. As your new InheritedWidget can potentially have the exact same child as before. And by exact, I mean the same instance.
Widgets who have the same instance they had before don't rebuild.
And in the most situation (Having an inheritedWidget at the root of your app), the inherited widget is constant. So no unneeded rebuild.