Introduction to the InheritedWidget of the shuttle series

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.

Tags: Android Flutter dart

Posted by ryclem on Mon, 20 Sep 2021 10:31:37 +0530