InheritedWidget introduction
When developing the interface of Flutter, we often encountered problems with data transfer. Because Flutter uses a tree of nodes to organize pages, a normal page has a very deep node hierarchy. At this point, if we still pass data layer by layer, it will be very troublesome when we need to obtain data of multiple parent nodes. Because of these problems, Flutter provides us with an InheritedWidget that gives all the children of a node access to the data under that node. The Scoped Model, BloC, and Provider are implemented based on InheritedWidget.
The source code used in this article is linked to a video tutorial
InheritedWidget source code analysis
As you can see, the source code for the InheritedWidget is very simple.
/// Abstract class InheritedWidget => Proxywidget => Widget
abstract class InheritedWidget extends ProxyWidget {
/// The constructor
/// Because the InheritedWidget is a Widget without an interface, the actual Widget needs to be passed in
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
/// Overrides the Widget createElement method of the superclass
@override
InheritedElement createElement() => InheritedElement(this);
/// Called when changes are made in the parent or ancestor widget (updateShouldNotify returns true).
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
Copy the code
InheritedWidget sample
Let’s use the InheritedWIdget as an example of an official counter
Step 1: Define an InheritedState class
- create
InheritedState
Inherited fromInheritedWidget
- Define a data that needs to be shared
count
- Define one for external fetching
InheritedState
The instanceof
methods - rewrite
updateShouldNotify
Method, whose primary purpose is to notify children that depend on the tree to share datawidget
import 'package:flutter/material.dart';
class InheritedState extends InheritedWidget {
/// A constructor
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 most recent current InheritedWidget for the component
static InheritedState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedState>();
}
/// Notify child widgets that rely on the tree to share data
@override
bool updateShouldNotify(covariant InheritedState oldWidget) {
return count != oldWidget.count;
}
}
Copy the code
Step 2: Write the layout and implement the counter effect with the InheritedWidget
- I created one first
InheritedCount
Components and subcomponentsWidgetA
andWidgetB
. - in
InheritedCount
To define acount
Variable for childrenwidget
To get thecount
The data. - use
InheritedState
Component and pass incount
Value, and subcomponents. - Child component
InheritedState
Shared data in. - in
InheritedCount
Button click changecount
Value, the child component data will be refreshed.
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) {
returnText(InheritedState.of(context)? .count.toString(), style: TextStyle( color: Colors.green, fontSize:50)); }}Copy the code
InheritedWidget source code analysis
In the counter example above, what is inheritedState.of (context) associated with WidgetB and InheritedWidget? . Count. The toString (), one of the most critical is the context. DependOnInheritedWidgetOfExactType (), We see dependOnInheritedWidgetOfExactType () Element in the source code is as follows: the code is in the framework. The dart line 3960
Map<Type, InheritedElement> _inheritedWidgets;
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
/// Assertion, used to detect if there are any ancestors in use (active) while debugging
assert(_debugCheckStateIsActiveForAncestorLookup());
/// Access to the_inheritedWidgets Array data
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if(ancestor ! =null) {
// assert whether the current ancestor is an InheritedElement type
assert(ancestor is InheritedElement);
// Returns and calls the update method
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
Copy the code
We can see that every Element instance holds a _inheritedWidgets, and every time we use this method, we take the InheritedElement instance of the related type from the collection object. So we don’t see how to set _inheritedWidgets in this method, so let’s look at how _inheritedWidgets are assigned.
// Element
void _updateInheritance() {
assert(_active); _inheritedWidgets = _parent? ._inheritedWidgets; }Copy the code
In the _updateInheritance method, we first assert whether the current node is active or not, and then assign values to the _inheritedWidgets of the parent node. We continue to look at the situation when _updateInheritance is called:
@mustCallSuper
void mount(Element parent, dynamicnewSlot) { ...... _updateInheritance(); . }@mustCallSuper
voidactivate() { ...... _updateInheritance(); . }Copy the code
We can see that in Element it calls the mount and activate functions, which means it is called every time an Element is mounted and remounted. When this method is executed, Element takes all of the inheritedElements from the upper layer. [InheritedElement] InheritedElement overrides the _updateInheritance method [InheritedElement]
@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;
}
Copy the code
How is InheritedWidget refreshed
InheritedElement takes all of the InheritedElment of the parent class and passes it down, and inheritedWidgets make all of the upper-layer inheritedWidgets accessible to the child widgets. So how does it refresh? In our Element dependOnInheritedWidgetOfExactType method calls the dependOnInheritedElement method, 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;
}
Copy the code
You can see that the InheritedElement instance calls its own updateDependencies method and passes the current Element instance to it
/// 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();
}
Copy the code
Because when InheritedElement is updated, the updated method is executed and notifyClients is called, iterating over all elements and calling the didChangeDependencies method.