Introduction to InheritedWidget
When developing the interface in fluent, we often encounter the problem of data transmission. Because fluent organizes pages in the way of node tree, the node level of an ordinary page will be very deep. At this time, if we still transfer data layer by layer, it will be very troublesome when we need to obtain the data of multiple parent nodes. Because of the above problems, fluent provides us with an InheritedWidget, which enables all child nodes under the node to access the data under the node. The Scoped Model, BloC and Provider are implemented based on InheritedWidget.
InheritedWidget source code analysis
You can see that the source code of inherited widget is very simple.
///Abstract class, inherited from Proxywidget, inheritance path inheritedwidwidget = > Proxywidget = > widget abstract class InheritedWidget extends ProxyWidget { ///Constructor ///Because InheritedWidget is a Widget without an interface, you need to pass in the actual Widget const InheritedWidget({ Key key, Widget child }) : super(key: key, child: child); ///Overriding superclass Widget createElement method @override InheritedElement createElement() => InheritedElement(this); ///Called when there is a change in the parent or ancestor widget (updateShouldNotify returns true). @protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }
InheritedWidget example
import 'package:flutter/material.dart'; import 'package:flutter_code/InheritedWidget/InheritedState.dart'; class InheritedCount extends StatefulWidget { @override _InheritedCountState createState() => _InheritedCountState(); } class _InheritedCountState extends State<InheritedCount> { int _count = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedDemo"), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _count++; }); }, child: Icon(Icons.add, color: Colors.white,), ), body: Center( child: InheritedState( count: _count, child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.center, children: [ WidgetA(), WidgetB() ], ) ), ), ); } } class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { return Text("widget text"); } } class WidgetB extends StatelessWidget { @override Widget build(BuildContext context) { return Text(InheritedState.of(context)?.count.toString(), style: TextStyle( color: Colors.green, fontSize: 50 ), ); } }
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class InheritedState extends InheritedWidget { ///Construction method InheritedState({ Key key, @required this.count, @required Widget child }): assert(count != null), super(key:key, child: child); ///Data to be shared final int count; ///Gets the current InheritedWidget of the component static InheritedState of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<InheritedState>(); } ///Notifies child widget s that depend on the tree to share data @override bool updateShouldNotify(covariant InheritedState oldWidget) { return count != oldWidget.count; } }
InheritedWidget source code analysis
In the above counter example code, the inheritedstate. Of (context)? Is associated between WidgetB and InheritedWidget Count. Tostring(), of which the most critical method is context.dependOnInheritedWidgetOfExactType(). Let's check the source code of dependOnInheritedWidgetOfExactType() in Element as follows: the code is in line 3960 of framework.dart
Map<Type, InheritedElement> _inheritedWidgets; @override T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) { ///Assertion, used to detect whether there is an ancestor in use (activated) in the debugging state assert(_debugCheckStateIsActiveForAncestorLookup()); ///Get_ Inheritedwidwidgets array data final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; if (ancestor != null) { // Assertion to determine whether the current ancestor is of InheritedElement type assert(ancestor is InheritedElement); // Return and call the update method return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }
It is not difficult to see that each Element instance holds one_ Inheritedwidwidgets. When this method is called for the first time, the InheritedElement instance of the relevant type will be taken from the collection object, so we don't see the setting in this method_ Let's take a look at the method of inheritedwidwidgets_ How inheritedWidgets are assigned.
// Element void _updateInheritance() { assert(_active); _inheritedWidgets = _parent?._inheritedWidgets; }
We found the assignment at_ In the updateInheritance method, it first asserts whether the current node is activated, and then through the parent node_ Inheritedwidwidgets are assigned values. Let's continue_ When will updateInheritance call:
@mustCallSuper void mount(Element parent, dynamic newSlot) { ...... _updateInheritance(); ...... } @mustCallSuper void activate() { ...... _updateInheritance(); ...... }
We can see that in element, it calls the mount and activate functions, that is, the method will be called every time the element is mounted and restarted. Then when the method is executed, element will get all inheritedelements from the upper layer. InheritedElement finally inherits the element, and you can see that InheritedElement rewrites the element_ updateInheritance method:
@override 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; }
How does the InheritedWidget refresh
We analyzed earlier that the InheritedElement will get all the inheritedelements of the parent class and pass them down. It is through this method that the InheritedWidget can make the following child widgets access all the inheritedwidgets in the upper layer. So how does it refresh? We call the dependOnInheritedElement method in the dependOnInherited Widget OfExactType method of Element. The code is as follows:
Set<InheritedElement> _dependencies; @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; } @protected void updateDependencies(Element dependent, Object aspect) { setDependencies(dependent, null); } @protected void setDependencies(Element dependent, Object value) { _dependents[dependent] = value; }
You can see that the InheritedElement instance calls its updatedependences method and passes the current Element instance to the past
/// Called during build when the [widget] has changed. /// /// By default, calls [notifyClients]. Subclasses may override this method to /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new /// widgets are equivalent). @protected void updated(covariant ProxyWidget oldWidget) { notifyClients(oldWidget); } @override void notifyClients(InheritedWidget oldWidget) { assert(_debugCheckOwnerBuildTargetExists('notifyClients')); for (final 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); } } } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); }
Because when the InheritedElement is updated, the updated method will be executed, and then continue to call notifyClients, traverse all elements and call the didChangeDependencies method.