What is InheritedWidget and How it works in a Flutter?
InheritedWidget is immutable and its attributes are final therefore, Flutter needs to rebuild Inherited Widget if we want to refresh it with new attributes. Also, the Provider plugin uses InheritedWidget. So in this article, we will learn about what is inheritedwidget and how it works in flutter.
When it comes to building robust and efficient user interfaces in Flutter, it’s crucial to grasp the concept of InheritedWidget. Flutter’s InheritedWidget is a powerful tool that allows data to be efficiently propagated down the widget tree, providing an elegant solution to managing state and sharing information across multiple widgets.
What is the purpose of InheritedWidget in flutter?
To pass data from an ancestor widget to descendant ones, which are possibly deep down the widget tree.
How does InheritedWidget Work In a Flutter?
Let’s understand with the help of a working example. consider a below screenshot like a below: Widget tree will look like a below:
- The MyContainer Widget can be clicked to increase the counter value in the CounterValue by 1.
- The CounterLabel Widget is a sibling of the CounterValue widget.
- The CounterValue should be automatically refreshed with a new counter value whenever the MyContainer is clicked, but the CounterLabel & the DummyContainer should not be rebuilt.
Consider a code snippet like the below:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'InheritedWidget playground', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: MyStatefulWidget( child: MyContainer( child: DummyContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CounterLabel(), CounterValue(), ], ), ), ), ), ), ); } } class MyStatefulWidget extends StatefulWidget { final Widget child; const MyStatefulWidget({Key key, @required this.child}) : super(key: key); static MyStatefulWidgetState of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data; } @override State<StatefulWidget> createState() { return MyStatefulWidgetState(); } } class MyStatefulWidgetState extends State<MyStatefulWidget> { int _counterValue = 0; int get counterValue => _counterValue; void addCounterBy1() { setState(() { _counterValue += 1; }); } @override Widget build(BuildContext context) { return MyInheritedWidget( child: widget.child, data: this, ); } } class MyInheritedWidget extends InheritedWidget { final MyStatefulWidgetState data; MyInheritedWidget({ Key key, @required Widget child, @required this.data, }) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidget oldWidget) { return true; } } class MyContainer extends StatelessWidget { final Widget child; MyContainer({ Key key, @required this.child, }) : super(key: key); void onPressed(BuildContext context) { MyStatefulWidget.of(context).addCounterBy1(); } @override Widget build(BuildContext context) { return Center( child: Container( width: 200, height: 200, child: RaisedButton( color: Colors.red, onPressed: (){ onPressed(context); }, child: child, ), ), ); } } class DummyContainer extends StatelessWidget { final Widget child; const DummyContainer({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return child; } } class CounterLabel extends StatelessWidget { @override Widget build(BuildContext context) { return Text( "Counter", style: TextStyle( color: Colors.white, fontSize: 20, ), ); } } class CounterValue extends StatefulWidget { @override State<StatefulWidget> createState() { return CounterValueState(); } } class CounterValueState extends State<CounterValue> { int counterValue; double fontSize; @override void didChangeDependencies() { super.didChangeDependencies(); MyStatefulWidgetState data = MyStatefulWidget.of(context); counterValue = data.counterValue; fontSize = 50.0 + counterValue; } @override Widget build(BuildContext context) { return Text( "$counterValue", style: TextStyle( fontSize: fontSize, color: Colors.white, ), ); } }
-
How does the CounterValue access the current counter value stored in the MyInheritedWidget?
MyInheritedWidget myInheritedWidget = context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
The depend OnInheritedWidget Of ExactType method enables a descendant Widget to access the closest ancestor MyInherited Widget instance enclosed in its BuildContext.
- How does Flutter find the closest ancestor MyInheritedWidget Instance from a widget’s BuildContext?
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; if (ancestor != null) { assert(ancestor is InheritedElement); return dependOnInheritedElement(ancestor, aspect: aspect); } _hadUnsatisfiedDependencies = true; return null; }
Every widget keeps a map _inheritedWidgets, which stores all ancestor Inherited widget instances indexed by their type.
- How is this _inherited widgets map constructed for each widget’s BuildContext in a widget tree?
- When an element is added to the widget tree, Flutter calls the mount method, which then invokes the _updateInheritance method, which copies the _inheritedWidgets map from the parent.
-
void _updateInheritance() { assert(_active); _inheritedWidgets = _parent?._inheritedWidgets; }
- The InheritedElement class overrides _updateInheritance to copy the _inheritedWidgets from the parent and then assigns itself to _inheritedWidgets with the key which is its runtime type.
void _updateInheritance() { assert(_active); final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets; if (incomingWidgets != null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement>(); _inheritedWidgets[widget.runtimeType] = this; }
By running the two above code snippets recursively when mounting a widget tree, every widget’s _inheritedWidgets stores all ancestor Inherited Widget instances.
- when the instance of MyInheritedWidget is rebuilt, the MyContainer and the CounterValue instances are rebuilt while the DummyContainer and CounterValue instances are not rebuilt.
- The body of the method dependOnInheritedWidgetOfExactType calls dependOnInheritedElement.
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; }
When the MyInheritedWidget is rebuilt…flutter loops through MyInherited Widget’s dependencies.
It then decides whether to rebuild these registered widgets by calling the updateShouldNotify method.
So if the method returns true, Flutter rebuilds all registered widgets. Otherwise, flutter does not.
void notifyClients(InheritedWidget oldWidget) { assert(_debugCheckOwnerBuildTargetExists('notifyClients')); for (Element dependent in _dependents.keys) { assert(() { // check that it really is our descendant Element ancestor = dependent._parent; while (ancestor != this && ancestor != null) ancestor = ancestor._parent; return ancestor == this; }()); // check that it really depends on us assert(dependent._dependencies.contains(this)); notifyDependent(oldWidget, dependent); } } void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); }
By calling didChangeDependencies on a dependent, the dependent is rebuilt.
Conclusion:
Thanks for being with us on a Flutter Journey !!!
Keep Learning !!! Keep Fluttering !!!
So now you also know What is InheritedWidget and How it Works in a Flutter.
Drop us your valuable suggestion/feedback to serve you better.
Contemporary ventures
Recent blog
ready to get started?
Fill out the form below and we will be in touch soon!
"*" indicates required fields