Before starting this article, if you haven’t read the previous articles, I suggest you read the following. Start with the StatelessWidget and enter the world of StatefulWidgets
In 2020 I completed an open source film project for Flutter as follows.
The App project of Flutter is based on Provider+MVVM
If you’re not familiar with the ProxyWidget class, you probably know that InheritedWidget is subclass InheritedWidget. It is one of the soul Settings in the Flutter Widget because the InheritedWidget is often used to do data sharing. For example, common Theme/ThemeData, Text/DefaultTextStyle, etc. are all shared using InheritedWidget, and the state management framework of Flutter is also implemented using it. For example, one of the most well-known state management frameworks is Provider.
ProxyWidget
ProxyWidget is the parent of the InheritedWidget, so what does it do? A widget that has A child widget provided to it, instead of building A new widget. The idea is that this is a part that provides child parts, rather than building new parts.
ProxyWidget source code:
abstract class ProxyWidget extends Widget {
/// Creates a widget that has exactly one child widget.
const ProxyWidget({ Key key, @required this.child }) : super(key: key);
/// The widget below this widget in the tree.
///
/// {@template flutter.widgets.child}
/// This widget can only have one child. To lay out multiple children, let this
/// widget's child be a widget such as [Row], [Column], or [Stack], which have a
/// `children` property, and then provide the children to that widget.
/// {@endtemplate}
final Widget child;
}
Copy the code
As you can see, the ProxyWidget abstract class is simple enough that it has only one child property inside it. So let’s move on to the inheritance. There are two subclasses InheritedWidget and ParentDataWidget that are subclasses of ProxyWidget. For those of you who have read the previous articles, it should be obvious to say Element after widget, because Element is really the method that provides rendering and widgets are just configuration items.
ProxyElement
/// An [Element] that uses a [ProxyWidget] as its configuration.
abstract class ProxyElement extends ComponentElement {
/// Initializes fields for subclasses.
ProxyElement(ProxyWidget widget) : super(widget);
@override
ProxyWidget get widget => super.widget as ProxyWidget;
@override
Widget build() => widget.child;
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget ! =null);
assert(widget ! = newWidget);super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
element.
@protected
void notifyClients(covariant ProxyWidget oldWidget);
}
Copy the code
Update, updated, and notifyClients are provided. The update method calls the updated method and the rebuild method is marked clean. The updated method calls the notifyClient method. Typically, subclasses will override it to implement their own logic before calling their parent’s updated method. The notifyClients method is an abstract method that is implemented by subclasses. (This will be mentioned later when analyzing subclasses.)
InheritedWidget
InheritedWidget is a subclass of the Implementation of The ProxyWidget and plays an important role in Flutter as described above. Let’s take a look at the source code as follows:
abstract class InheritedWidget extends ProxyWidget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
/// Whether the framework should notify widgets that inherit from this widget.
///
/// When this widget is rebuilt, sometimes we need to rebuild the widgets that
/// inherit from this widget but sometimes we do not. For example, if the data
/// held by this widget is the same as the data held by `oldWidget`, then we
/// do not need to rebuild the widgets that inherited the data held by
/// `oldWidget`.
///
/// The framework distinguishes these cases by calling this function with the
/// widget that previously occupied this location in the tree as an argument.
/// The given widget is guaranteed to have the same [runtimeType] as this
/// object.
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
Copy the code
The interior is fairly simple. The first thing you need to do is create its corresponding Element, and then provide an abstract method called updateShouldNotify. This method is used to control whether or not data is synchronized, that is, whether or not a child component needs to be rebuilt if its value changes when referencing the InheritedWidget’s parent component.
InheritedElement
class InheritedElement extends ProxyElement {
/// Creates an element that uses the given widget as its configuration.
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get widget => super.widget as InheritedWidget;
final Map<Element.Object> _dependents = HashMap<Element.Object> ();@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;
}
@override
void debugDeactivated() {
assert(() {
assert(_dependents.isEmpty);
return true; } ());super.debugDeactivated();
}
@protected
Object getDependencies(Element dependent) {
return _dependents[dependent];
}
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(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); }}}Copy the code
We will see that the “_inheritedWidgets” map is inside the parent Element. We will start with the StatelessWidget. So what is it doing with the InheritedElement? Save the current InheritedWidget object. Why save it? Again, the InheritedWidget is used to find the parent class using the child component’s context. We want to use InheritedWiget inside data, flutter he provides several methods for us, one way is to “context. GetElementForInheritedWidgetOfExactType ()”. It needs to use _inheritedWidgets to get the InheritedWidget object it wants.
Gets the InheritedWidget object for the parent component
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}
Copy the code
Just get the InheritedWidget object without doing anything else as described above. So it’s just getting objects.
Look at the “dependOnInheritedWidgetOfExactType” source code:
@override
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) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
Copy the code
[inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] But unlike “getElementForInheritedWidgetOfExactType” method then calls “dependOnInheritedElement (ancestor, the aspect: the aspect) as T” method. Take a look at its source code:
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor ! =null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
Copy the code
Internally, we call the updateDependencies method on the InheritedElement to pass in the current elment.
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
Copy the code
The inside of the method then calls the setDependencies method to pass the current Element along.
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
Copy the code
Internally, the setDependencies method saves the current elment as a _dependents HashMap. final Map
_dependents = HashMap
();
The InheritedWidget updates the data
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
Copy the code
Note that the updated method of the parent class needs to be called “updateShouldNotify” before calling its updated method. This method is an abstract method of the InheritedWidget and requires its subclasses to implement it. This method also controls whether the data is synchronized, so that the operation can be controlled by subclasses. What does the updated method that calls parent ProxyElement do?
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@protected
void notifyClients(covariant ProxyWidget oldWidget);
Copy the code
The updated method on the InheritedElement calls notifyClients, which is an abstract method implemented by subclasses.
@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); }}Copy the code
It calls the “notifyDependent” method through the current InheritedElement object of “_dependencies”.
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
Copy the code
Internally, Element’s “didChangeDependencies” method is called. This is why the child component’s didChangeDependencies method is called when it relies on InheritedWidget data, and markNeedsBuild is re-built in its method. At this point, the source code analysis of the InheritedWidget is complete.