From the previous Flutter update mechanism, we know that the updates of single node and multi-node elements all update their child nodes. Although Flutter does a good job of diff and reuse, it inevitably causes redundancy in computation. Is there a way to implement directional refresh? This is the InheritedWidget.

👇 Here’s a look at how we use and work with inheritedWidgets.

1 InheritedWidget profile

The official documentation of Flutter is great. Let’s look at its description below:

Base class for widgets that efficiently propagate information down the tree. To obtain the nearest instance of a particular type of inherited widget from a build context, use [BuildContext.dependOnInheritedWidgetOfExactType]. Inherited widgets, when referenced in this way, will [cause the consumer to rebuild] when the inherited widget itself changes state.

InheritedWidget is a base class that efficiently passes information along the node tree. It is important to note that ⚠️ transmits information from top to bottom, so its data flow is monomial and it must be the ancestor node of the transmitted information. Children want to get the recent InheritedWidget, can use BuildContext. DependOnInheritedWidgetOfExactType method, child nodes InheritedWidget referenced by this method to the ancestors, When the state of the ancestor InheritedWidget changes, the child node is also rebuilt (the build method is executed).

We can see the features of the InheritedWidget in the description above:

  • To use an InheritedWidget, you must inherit it
  • The InheritedWidget must be an ancestor node or have a high hierarchy of nodes
  • When the InheritedWidget’s state changes, it builds child nodes that reference itself

In addition to the above class description, the documentation also provides examples of how to use the class, and common usage methods.

The convention is to provide a static method of on the [InheritedWidget] which does the call to [BuildContext.dependOnInheritedWidgetOfExactType]. This allows the class to define its own fallback logic in case there isn’t a widget in scope.

Common practice is, in our custom InheritedWidget class, define a static of method, the method to invoke BuildContext. DependOnInheritedWidgetOfExactType method. This makes it easier to do some business logic in the of method, such as refreshing or not refreshing child nodes, handling null cases, and so on.

From the description above, we can know its usage:

  • Define a class that InheritedWidget
  • Define a static of method to look up

Let’s look at how it’s used in a real case.

2 Use Cases

Take the counter project as an example.

2.1 Customize the InheritedWidget

class MyInheritedWidget extends InheritedWidget {
  
  / / InheritedWidget ProxyWidget
  //child for display
  MyInheritedWidget(Widget child) : super(child: child);

  // The Boolean value of this method, which determines whether the child node is refreshed
  // To avoid invalid refresh of the same data
  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true; }}Copy the code

2.2 Package Data

The counter case manages simple numbers, so we can pass them in through the constructor. D More often than not, the wrapped data is more complex, such as MediaQuery screen data, Theme data, etc.

class MyInheritedWidget extends InheritedWidget {
  // Descendant nodes use count data
  final int count;

  MyInheritedWidget(Widget child, this.count) : super(child: child);

  // Here you can update the timing
  // Data is inconsistent
  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    returnoldWidget.count! =this.count; }}Copy the code

2.3 Adding the of method

class MyInheritedWidget extends InheritedWidget {
  final int count;

  MyInheritedWidget(Widget child, this.count) : super(child: child);

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    returnoldWidget.count! =this.count; }}Copy the code

The purpose of adding the of method is to encapsulate, encapsulate the business logic, encapsulate the unified entry point. Here we return a custom MyInheritedWidget that can also return data.

.

2.4 Project Transformation

The specific code is 👉. Here’s part of the code.

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  CenterWidget centerWidget;

  @override
  void initState() {
    super.initState();
    // Construct the word tree
    centerWidget = new CenterWidget();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      // Construct a high-level InheritedWidget node
      body: MyInheritedWidget(centerWidget, _counter),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}class MyInheritedWidget extends InheritedWidget {
  final int count;

  MyInheritedWidget(Widget child, this.count) : super(child: child);

  // Package quick lookup method
  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    returnoldWidget.count! =this.count; }}class CenterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('CenterWidget');
    returnCenter( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ TextDescWidget(), TextCounterWidget(), ], ), ); }}class TextDescWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('TextDescWidget');
    return Text(
      'You have pushed the button this many times:',); }}class TextCounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('TextCounterWidget');
    return Text(
      '${MyInheritedWidget.of(context).count}', style: Theme.of(context).textTheme.display1, ); }}Copy the code

We take an original file, break it down into several components, and put it together. If we do not use the InheritedWidget, the counter value needs to be passed from scaffolding >CenterWidget >TextCounterWidget. Now all you need to do is pass the data down through the OF method.

2.6 Directional Refresh

As written above, the number changes when we click the button, and only the Build method of the TextCounterWidget is executed.

2.7 Usage Summary

  • A custom Widget inherits a self-inheritedWidget
  • Customize wrap data parameters in widgets
  • Write static of methods to retrieve data or custom widgets
  • Will implement the data to monitor dependOnInheritedWidgetOfExactType lookup

Three principles

We implement two functions with InheritedWidget:

  • Up to find

Children nodes through dependOnInheritedWidgetOfExactType method found the recent upward InheritedWidget of the specified type

  • Directional refresh

Inheritedwidgets refresh only data-focused descendant widgets

Let’s see how it works.

3.1 Searching Upward

We look at the method introduction dependOnInheritedWidgetOfExactType.

Obtains the nearest widget of the given type [T], which must be the type of a concrete [InheritedWidget] subclass, and registers this build context with that widget such that when that widget changes (or a new widget of that type is introduced, or the widget goes away), this build context is rebuilt so that it can obtain new values from that widget.

Calling this method is O(1) with a small constant factor, but will lead to the widget being rebuilt more often.

This method is an instance of BuildContext, which gets the most recent InheritedWidget of the specified type and registers the called context instance. When the InheritedWidget data of the specified type changes, The build method of the context is called. And the upward-looking time complexity of this method is O(1).

From the above description, we can know that:

  • DependOnInheritedWidgetOfExactType role has two: search, register
  • The time of the search is order one.

It is natural to think of the search process recursively to see if your parent is a Widget of the specified type, as shown in the following figure:

We see that the time complexity of a Flutter is O(n) in extreme cases. How does the Flutter achieve O(1)? Search efficiency is relatively fast is continuous array, hash map. Flutter is implemented by map. The Element class of Flutter maintains a Map member variable [_inheritedWidgets]. The Map key is the type of the Widget and the value is the specific Element. We know that the mount method is the mount method before an Element can be displayed on the screen.


  void mount(Element parent, dynamic newSlot) {
    ...
    // Specify the parent node and the hierarchy_parent = parent; _slot = newSlot; _depth = _parent ! =null ? _parent.depth + 1 : 1;
    _active = true; .// The key is this method
    _updateInheritance();
  }
Copy the code
Void _inheritedWidgets () {_inheritedWidgets = _parent? ._inheritedWidgets; }Copy the code

This means that when an Element is mounted, it assigns its parent’s map to itself. The only implementation class for the _updateInheritance method is InheritedElement, which is the Element behind the InheritedWidget.

  @override
  void _updateInheritance() {
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    //如果父节点的map是null,就new一个
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    //在map中保存Widget的类型和自己  
    _inheritedWidgets[widget.runtimeType] = this;
  }
Copy the code

Here are three cases:

Case a



The C node and its ancestor are common elements, so its ancestor Eelement’s map is null, so use _inheritedWidgets = _parent? ._inheritedWidgets, which itself has a null map.

D’s ancestor map is null, but D is an InheritedElement node. By overriding the _updateInheritance method, its map is {Widget Type: D object itself}.

The E node is a normal Element node and its map is the map of the D node. When E looks up, it directly finds the DElement object. The efficiency of the search is also O(1).

Case 2



The A node is A normal Element, so its map is null. {TypeInheritedWidget:B object} Node B is the Element of the TypeInheritedWidget, so its map is {TypeInheritedWidget:B object} node C is the normal node, and its map is the map using the parent node. So its map is an Element of the TypeInheritedWidget, even though it inherits the parent’s map. But _inheritedWidgets[widget.runtimeType] = this; After this code, its map is {TypeInheritedWidget:D object}. When the E node looks up the Element of the TypeInheritedWidget, it gets the D object, not the B object, which is the principle of nearness. To sum up: Flutter uses a Map to look up O(1), not recursion.

3.2 Directional Refresh

From the last article, we learned that updating an Element calls the update method. So let’s look at the InheritedElement update method.

Void updated (InheritedWidget oldWidget) {/ / if you need to inform child node is invoked update the if (widget. UpdateShouldNotify (oldWidget)) super.updated(oldWidget); }Copy the code

Remember that our overwritten method, updateShouldNotify, does not update if it returns fase, so we need to determine whether to return true based on the data of the old and new widgets. Now let’s look at the super update. The update to super is simple, with a notification method called.

  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
Copy the code

See here, a little bit of clarity 🤏. InheritedElement updates notify data-focused children. So let’s move on.

void notifyClients(InheritedWidget oldWidget) { for (Element dependent in _dependents.keys) { notifyDependent(oldWidget,  dependent); } } void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); }Copy the code

The _dependents.keys collection is a collection of children that call their own didChangeDependencies method when the data changes. We know that there are many types of elements, each with its own processing logic.

category Processing logic case
StatefulElement _state.didChangeDependencies() Image Image implementation logic
StatelessElement The reconstruction The development process has no perception

Keys > < span style = “box-sizing: inherit! Important; color: inherit! Important; Answer: is what we call dependOnInheritedWidgetOfExactType from time to time.

@ override T dependOnInheritedWidgetOfExactType < T extends InheritedWidget > ({Object aspect}) {/ / this is what we mentioned above to find upwards, Final InheritedElement ancestor = _inheritedWidgets == null? null : _inheritedWidgets[T]; if (ancestor ! = null) { _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); / / this place is a registered ancestor. UpdateDependencies (this aspect); return ancestor.widget; } _hadUnsatisfiedDependencies = true; return null; } void updateDependencies(Element dependent, Object aspect) { setDependencies(dependent, null); } void setDependencies(Element dependent, Object value){_dependents[dependent] = value; }Copy the code

We see that this method does two things: ① the map looks for ancestors and ② updates the dependencies of the ancestors found. So, _dependents.keys is the child node of all the dependent data. So is there a way to just look up without registering? That is findAncestorWidgetOfExactType method.

/ / or loop search only T findAncestorWidgetOfExactType < T extends widgets > () {Element ancestor = _parent; while (ancestor ! = null && ancestor.widget.runtimeType ! = T) ancestor = ancestor._parent; return ancestor? .widget; }Copy the code

summary

DependOnInheritedWidgetOfExactType can realize: search and registration, and find the way of implementation is the Map, the efficiency is O (1) findAncestorWidgetOfExactType only can realize lookup.

conclusion

We’ve covered the use of the InheritedWidget and the underlying underlying implementation of the InheritedElement. Combined with the single and multi-node update mechanisms of the previous article, the Flutter update mechanism is now over.