background

Native StatefulWidget provides the stateful component to manage the state. For multi-component state interaction, the parent component can be used to manage and distribute the state. However, once the business is complex and there are enough branches in the component tree, the state will sink deeply and the state transfer will be complicated.

The simple case looks like this:

As you add functionality, your application will have dozens or even hundreds of states. Your application should look something like this:

The above is actually the scenario where multiple pages need to share state and transfer information. The direct approach is as follows:

  1. Distributing notifications through the parent widget has a deep nesting level problem, and the setState of the parent level causes unnecessary build problems

  2. Through callback pass, there is also the problem of deep pass, callback will also miss the call problem

Problems faced

  1. How to get a data source

  2. How to update the data source

  3. How do I notify components of data source updates

  4. How are data sources shared across components

Data flow concept

State management will be based on one-way data flow, here is the concept of data flow

Unidirectional data flow

  • State: Data source that drives the application.

  • View: map state to the view declaratively.

  • Actions: Changes in state in response to user input on the view

State management of one-way data flows: By defining and isolating concepts in state management and enforcing compliance with certain rules, our code becomes more structured and maintainable

Features: (1) All state changes can be recorded, traceable, easy to trace the source; (2) There is only one copy of all data, and only one entrance and exit of component data, which makes the program more intuitive and easier to understand and conducive to the maintainability of the application; (3) Once the data changes, to update the page (data-> page), but did not (page ->data); (4) If users make changes on the page, it will be collected manually (two-way automatic) and merged into the original data

Two-way data flow

Bidirectional data binding brings bidirectional data flow, bidirectional binding between data (state) and View (View).

Ng-model for NG and V-Model for VUE, and DataBinding for Android

What it boils down to is a syntactic sugar (one-way binding of value + onChange event listening)

Features: (1) No matter the data changes, or user operations, can bring mutual changes, automatic update. Applies to project details, such as UI controls (usually form-like operations). (2) The change of state is uncontrollable

The solution

StatefulWidget

The official stateful component can manage various states in its component by itself or by the parent component. Which management is appropriate? Generally, the following principles are followed:

  • If the state is user data, such as the checked status of a checkbox, or the position of a slider, that state is best managed by the parent Widget.
  • If the state is about the appearance of the interface, such as color or animation, then the state is best managed by the Widget itself.
  • If a state is shared by different widgets, it is best managed by their common parent Widget.

In general, it is better to manage state encapsulation inside the Widget, while managing state in the parent Widget is more flexible. If you do not know whether it should be managed by the Widget itself, design it as a StatelessWidget in preference to the parent Widget

Question:

In the case of single function and complex multiple pages, state sinking is too deep, state transfer is complex, and the rebuild scope is too large

InherityWidget

Functional components provide a mechanism for data to be passed from top to bottom in the widget tree and shared

For example, if a state is shared in the root Widget with an InherityWidget, it can be acquired in any of the child widgets. Theme,Navigator, and so on are shared across the entire app using this mechanism, eliminating the need for cascading

use

  1. A parent Widget used to store shared data that inherits the InheritedWidget
class FatherWidget extends InheritedWidget { final int data; FatherWidget({@required this.data, Widget child}) : super(child: child); Static FatherWidget of(BuildContext context) {returncontext.inheritFromWidgetOfExactType(FatherWidget); Override bool updateShouldNotify(FatherWidget oldWidget) {// Override bool updateShouldNotify(FatherWidget oldWidget) {return oldWidget.data != data;
  }
}
Copy the code
  1. Child widgets that get state and handle responses when dependencies change

    class ChildWidget extends StatefulWidget {
      @override
      _ChildWidgetState createState() => _ChildWidgetState();
    }
    
    class _ChildWidgetState extends State<ChildWidget> {
      @override
      Widget build(BuildContext context) {
        return new Text(FatherWidget.of(context).data.toString());
      }
    
      @override
      void didChangeDependencies() { super.didChangeDependencies(); // The InheritedWidget in the parent or ancestor widget changes (updateShouldNotify returnstrue// If there is no InheritedWidget in the build, this callback will not be calledprint("didChangeDependencies = "+ FatherWidget.of(context).data.toString()); }}Copy the code
  2. integration

    class ContainerWidget extends StatefulWidget { @override _ContainerWidgetState createState() => _ContainerWidgetState();  } class _ContainerWidgetState extends State<ContainerWidget> { int _data = 0; void_incrementCounter() {
        setState(() { _data++; /// change the state}); } @override Widget build(BuildContext context) {returnFatherWidget( data: widget.data, child: ChildWidget(), ); }}Copy the code

graphic

  1. How to implement a subtree to get an InheritedWidget

  1. How do inheritedWidgets connect with child widgets that get it with “of”

The key point

“DidChangeDependencies” :

One of the lifecycle functions in State that is notified by a Framework call when a dependency changes, in this case whether the child Widget uses the InherityWidget’s data

In addition, this callback is executed immediately after initState, which can be used by retrieving.context directly

The source code

How to implement a subtree to get an InheritedWidget directly

When we create a build InheritedWidget, the corresponding Element calls the following _updateInheritance method: Copy _inheritedWidgets from the parent Element and register this InheritedElement into _inheritedWidgets

class InheritedElement extends ProxyElement { final Map<Element, Object> _dependents = HashMap<Element, Object>(); . @override void_updateInheritance() {
  ...
    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

The acquisition process is as follows

Call the of method static ThemeData of(BuildContext Context, {bool shadowThemeOnly =false}) { final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme); . } /// Element class @override InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) { ... final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; /// Take [InheritedElement] by typeif(ancestor ! = null) { assert(ancestor is InheritedElement);returninheritFromElement(ancestor, aspect: aspect); / / / a child Element and InheritedElement relationship here and return InheritedWidget} _hadUnsatisfiedDependencies =true;
    returnnull; } @override InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) { ... _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);return ancestor.widget;
  }
Copy the code

Note: There is also a way to only take an InheritedElement without registering the dependencies

/// Element class
@override
  InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
	...
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    return ancestor;
  }
Copy the code

conclusion

Advantages:

  1. Automatically subscribe

  2. State can be obtained across components

Disadvantages:

  1. Each time you force the inheritedWidget build to rebuild, virtually all of the subtrees’ builds are triggered, so a StatefulWidget needs to be wrapped to implement cache loading

  2. There is no effective separation of view logic and business logic.

  3. Unable to direct/directional notification. In fact, child widgets that rely on InheriteWidget call markNeedsBuild at the Element level before calling State’s didChangeDependencies, so they all rebuild

reference

  1. One-way data flow and two-way data flow