Follow the previous article on the Flutter state management Provider(I) for easy use

Process analysis

The State of the setState

We debug the code for the example above. Enter the setState

 void setState(VoidCallback fn) { final dynamic result = fn() as dynamic; _element.markNeedsBuild(); /// Element}Copy the code

Let’s go to Element

void markNeedsBuild() {
   if(! _active)return;
   if (dirty) return;
   _dirty = true;
   owner.scheduleBuildFor(this); ///BuildOwner
 }
Copy the code

BuildOwner. ScheduleBuildFor: add the current element to the dirty list. DrawFrame Reconstructs the dirty Element

/// Adds an element to the dirty elements list
/// so that it will be rebuilt  
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
  if (element._inDirtyList) {
      return true; } ());if(! _scheduledFlushDirtyElements && onBuildScheduled ! = null) { _scheduledFlushDirtyElements =true;
    onBuildScheduled();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
}
Copy the code

The rebuilt call stack looks like this.

-- > onBuildScheduled () (not a must)... - > SchedulerBinding. ScheduleFrame ()... -- -- > WidgetsBinding. DrawFrame () - > BuildOwner.buildScope() --> Element.rebuild() --> ComponentElement.performBuild() --> StatefulElement.build()/updateChild(_child, built, slot) --> State.build()Copy the code

See the article for more details

InheritedWidget

SetState rebuilds everything mindlessly with element.markNeedsbuild (). Next up is the InheritedWidget, which focuses on two issues:

  • How do you share data?
  • How do I control rebuild?

Start with the use of the InheritedWidget

////CountProvider extends InheritedWidget {final int count; CountProvider({Key key, this.count, Widget child}) : super(key: key, child: child); @override bool updateShouldNotify(CountProvider old) {return true; } // Get CountProvider provider1 = BuildContext.getElementForInheritedWidgetOfExactType<CountProvider>().widget; var count=provider1.count;Copy the code

The BuildContext object starts with a section comment

[BuildContext] objects are actually [Element] objects. The [BuildContext] interface is used to discourage direct manipulation of [Element] objects.

The BuildContext object is really an Element object. The BuildContext interface is used to block direct access to Element objects. Obviously, Element is an implementation detail, and the system doesn’t want us to manipulate it directly.

InheritedWidget teaches us that there are two ways to get an InheritedWidget. 1.12.13 replaces the 1.6 method and makes both methods more literal.

/ / / the first kind, get InheritedElement obtained it InheritedWidget InheritedElement Element. GetElementForInheritedWidgetOfExactType < T extends InheritedWidget > Element () / / / the second T. DependOnInheritedWidgetOfExactType < T extends InheritedWidget > ();Copy the code

InheritedElement is what? We go to the InheritedWidget.

The InheritedWidget tells us two things:

  • The corresponding Element is InheritedElement. So the focus is on InheritedElement.
  • UpdateShouldNotify when we salvage itedWidget that we don’t always need to rely on its widgets. Yes or no, depending on your updateShouldNotify method.
abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
Copy the code

Let’s move on to the first one

class Element{ Map<Type, InheritedElement> _inheritedWidgets; @ override InheritedElement getElementForInheritedWidgetOfExactType < T extends InheritedWidget > () {/ / / the key for T Type, InheritedElement final InheritedElement ancestor = _inheritedWidgets == null? null : _inheritedWidgets[T];returnancestor; } // we found that _updateInheritance() had modified _inheritedWidgets void_updateInheritance() { assert(_active); _inheritedWidgets = _parent? ._inheritedWidgets; _updateInheritance() is called from mount() or active(). Mount () adds element to element Tree for the first time. /// Active () re-marks the deactivate element as active. // @mustCallSuper void mount(Element parent, dynamic newSlot) { _parent = parent; _slot = newSlot; _depth = _parent ! = null ? _parent.depth + 1 : 1; _active =true;
   _updateInheritance();
 }
 
 @mustCallSuper
 void activate() {
   _active = true; _dependencies? .clear(); _hadUnsatisfiedDependencies =false;
   _updateInheritance();
 }
Copy the code

The InheritedWidget corresponds to the InheritedElement. It overrides the _updateInheritance method and adds itself to it, which is also something you can imagine.

class 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

In retrospect, it was pretty simple. The current Element InheritedElement of the parent. So no matter how many layers are left between an Element and an InheritedElement, as long as it’s a childElement, it can be passed on.

We answered the first question — how to share data.

Now we entered dependOnInheritedWidgetOfExactType

class Element { @override T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) { / / / this period of operation and getElementForInheritedWidgetOfExactType as like as two peas. final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; /// Add dependencies.if(ancestor ! = null) { assert(ancestor is InheritedElement);return dependOnInheritedElement(ancestor, aspect: aspect);
   }
   _hadUnsatisfiedDependencies = true;
   returnnull; } @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor ! = null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);returnancestor.widget; }}Copy the code

We’ll enter the ancestor updateDependencies, did what things.

class InheritedElement{
   @protected
   void updateDependencies(Element dependent, Object aspect) {
       setDependencies(dependent, null);
   } 
   
   @protected
   void setDependencies(Element dependent, Object value) { _dependents[dependent] = value; }}Copy the code

The summary is that the current Element and InheritedElement preserve each other’s dependencies.

Let’s recall the custom ChangeNotifierProvider from the previous article.

class ChangeNotifierProvider extends StatefulWidget { final Widget child; final CountModel model; . } class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> { _ChangeNotifierProviderState();_update() {
    setState(() => {});
  }
  
  @override
  void initState() { super.initState(); widget.model.addListener(_update); }... // override Widget build(BuildContext context) {returnCountProvider( model: widget.model, child: widget.child, ); }}Copy the code

But when I hit the Increment button,

--> ChangeNotifier._update()
--> ChangeNotifierProvider.setState()
--> ...
--> Element.rebuild() 
--> ComponentElement.performBuild()
--> StatefulElement.build()/updateChild
--> ChangeNotifierProvider.build(BuildContext)
Copy the code

Let’s go to ComponentElement

Class ComponentElement {/ / / ChangeNotifierProvider corresponding ComponentElement performRebuild @ override voidperformRebuild() {/ / / call _ChangeNotifierProviderState. The build (), generate new CountProvider Widget built = build (); _child = updateChild(_child, built, slot); }} // we are using Element InheritedElement NewWidget is our new build CountProvider @protected Element updateChild(Element Child, Widget newWidget, Dynamic newSlot) {///newWidget is null, making the child inactivated invisible and not cleared.if (newWidget == null) {
      if(child ! = null) deactivateChild(child);return null;
    }
    if(child ! Const const CountProvider = null) {/// new and old CountProvider are the same object, handle slot, and visibilityif (child.widget == newWidget) {
        if(child.slot ! = newSlot) updateSlotForChild(child, newSlot);return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); //* Only here is InheritedElement child.update(newWidget); //* Only here is InheritedElement child.update(newWidget);return child;
      }
      deactivateChild(child);
    }
    return inflateWidget(newWidget, newSlot);
  }
Copy the code

Following the child.update(newWidget) above, ProxyElement is entered first

class ProxyElement{
    @override
    void update(ProxyWidget newWidget) {
        final ProxyWidget oldWidget = widget;
        super.update(newWidget);
        assert(widget == newWidget);
        updated(oldWidget);
        _dirty = true; rebuild(); } @protected void updated(covariant ProxyWidget oldWidget) { notifyClients(oldWidget); }}Copy the code

InheritedElement overloads the updated() method. NotifyClients () we find that we have left out a trick here, which is the updateShouldNotify we need to implement

class InheritedElement{
    @override
    void updated(InheritedWidget oldWidget) {
    if(widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); } override void notifyClients(InheritedWidget oldWidget) {$InheritedWidget ($InheritedWidget oldWidget) / / / by dependOnInheritedWidgetOfExactType Element dependent childrenfor (Element dependent in_dependents.keys) { notifyDependent(oldWidget, dependent); } } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {/// finally call dependent's markNeedsBuild(); dependent.didChangeDependencies(); }}Copy the code

Here we sort of make the method call stack clear. And get a sense of the inner workings of the InheritedWidget. This is the observer mode: register to listen and notify the logic of updates. More details are still being learned.

This is enough to get us into the process of using the Provider.

ChangeNotifierProvider

Here we pick the commonly used ChangeNotifierProvider to analyze and review code

class CountModel extends ChangeNotifier {
 int count;
 CountModel(this.count);
 void increment() {
   count++;
   notifyListeners();
 }
}

class _ProviderDemoWidget4State extends State<ProviderDemoWidget4> {
 CountModel _countModel = CountModel(0);

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(providerDemoTitle4),
     ),
     body: ChangeNotifierProvider.value(
       value: _countModel,
       child: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             Consumer<CountModel>(
               builder: (contextC, model, child) {
                 return Text("Count:${model.count}(with dependency)");
               },
             ),
             Builder(builder: (context2) {
               return Text(
                   "Count:${Provider.of<CountModel>(context2, listen: false).count}(no dependence)");
             }),
             RaisedButton(
                 child: Text("increment"), onPressed: () => _countModel.increment()), Text(providerDemoIntroduction4), ], ), ), ), ); }}Copy the code

InheritedProvider class diagram

ChangeNotifierProvider ultimately inherits the InheritedProvider. The InheritedProvider is the center of the whole Provider. Its key components: _DelegateState _InteritedProviderScopeMixin.

_DelegateState

The InheritedProvider delegates the following functionality to _Delegate/_DelegateState.

  • Data T assignment,
  • Listen to Settings,
  • shouldUpdate

There are _ValueInheritedProvider and _CreateInteritedProvider based on the data T assignment. _DelegateState does not directly hold InteritedElement, but hold MixinOn InteritedElement of _InteritedProviderScopeMixin (InteritedElement custom part)

_InteritedProviderScopeMixin
  • _DefaultInheritedProviderScopeElement InteritedElement inheritance. At the same time put a lot of custom parts, in the _InteritedProviderScopeMixin, such as performRebuild, build, update, and so on
  • _DefaultInheritedProviderScopeElement only provides the necessary glue code, widgets, delegate, various associated code element.

ChangeNotifierProvider initialization process

Start with the sample code above. We debug _ProviderDemoWidget4State in the build method and make a breakpoint. Call stack found

-> StatefulElement(ProviderDemoWidget4).performRebuild() -> StatefulElement(ProviderDemoWidget4).build():Scaffold ---> ProviderDemoWidget4State.build():Scaffold -> StatefulElement.updateChild(_child, built, slot) ---> StatefulElement.inflateWidget(newWidget, newSlot) -----> Scaffold.createElement():StatefulElement -----> StatefulElement(Scaffold).mount(this, newSlot) -------> StatefulElement(Scaffold)._firstBuild() ---------> StatefulElement(Scaffold).rebuild() -----------> StatefulElement(Scaffold).performRebuild() -------------> StatefulElement(Scaffold).build():_ScaffoldScope -------------> ScaffoldState.build():_ScaffoldScope .....Copy the code

We make a breakpoint on the next line of the performRebuild() call to build(). After skipping over and over again, we finally found our ChangeNotitierProvider

-->StatelessElement(_bodyBuilder).performRebuild()
---->StatelessElement(_bodyBuilder).build():ChangeNotitierProvider
---->StatelessElement(_bodyBuilder).updateChild()
------>StatelessElement(_bodyBuilder).inflateWidget()
-------->ChangeNotitierProvider.createElement():_InheritedProvderElement
-------->_InheritedProvderElement.mount(this, newSlot)
---------->_InheritedProvderElement._firstBuild()
------------>_InheritedProvderElement.rebuild()
-------------->_InheritedProvderElement.performRebuild()
---------------->_InheritedProvderElement.build():_DefaultInheritedProviderScope
------------------>InheritedProvder.buildWithChild():_DefaultInheritedProviderScope
Copy the code

Because this place IntertiedProvider inherited SignleChildStatelessWidget. According to the call stack, into InheritedProvder buildWithChild, _DefaultInheritedProviderScope at this time, also appeared on the horizon.

_InheritedProvderElement. After in the build get _DefaultInheritedProviderScope performRebuild, continue to implement updateChild, because is the initialization process, You must be in the inflateWidget()

-->_InheritedProvderElement.inflateWidget()
---->_DefaultInheritedProviderScope.createElement():_DefaultInheritedProviderScopeElement
---->_DefaultInheritedProviderScopeElement.mount(parent,newSlot)
Copy the code

Then enter _DefaultInheritedProviderScopeElement, mention it in front of the most function in _InheritedProviderScopeMixin.

So to enter _InheritedProviderScopeMixin. PerformRebuild (). If it’s _firstBuild, then _mountDelegate(), I think association is more appropriate. Super. performRebuild is also implemented to build your own Widget and updateChild.

class _DefaultInheritedProviderScopeElement{
    @override
    void performRebuild() {
        if (_firstBuild) {
        _firstBuild = false;
        _mountDelegate();
        }
        super.performRebuild();
    }
    
     @override
    void _mountDelegate() {
        _delegateState = widget.owner._delegate.createState()..element = this;
    }
}
Copy the code

Where did the _delegate come from? From ChangeNotifierProvider. We value construction method in all the way to come in, find InheritedProvder _delegate _ValueInheritedProvider.

  • The _delegateState _mountDelegate: _DefaultInheritedProviderScopeElement InheritedProvider reference. Also, _delegateState references element. Let’s do it in relation to each other.

_DefaultInheritedProviderScopeElement inherited InheritedElememnt. Build () method to inform _delegateState execution. The build (), but because it is initialized, It also does not trigger the logic of _delegatestate.build. _shouldNotifyDependents is also false. This logic is useful when the notifyListeners trigger a refresh. Super. The build () will return _DefaultInheritedProviderScope (extend InheritedWidget). The child, namely ChangeNotifierProvider. The value of the parameters of the child. Child is the part of our actual layout.

class _DefaultInheritedProviderScopeElement{
  @override
  Widget build() {
    if (_isLazy(widget) == false) {
      value; // this will force the value to be computed.
    }
    _delegateState.build(_isBuildFromExternalSources);
    _isBuildFromExternalSources = false;
    if (_shouldNotifyDependents) {
      _shouldNotifyDependents = false;
      notifyClients(widget);
    }
    return super.build();
  }
Copy the code

Enter the Child custom section and find that there are two ways to obtain the Model: Consumer and Provider.

    Consumer<CountModel>(
        builder: (contextC, model, child) {
            return Text("Count:${model.count}(with dependency)");
        },),
    Builder(builder: (context2) {
        return Text(
            "Count:${Provider.of<CountModel>(context2, listen: false).count}(no dependence)");
        }),
Copy the code

Consumer encapsulates provider. of(context), and default listen is true. Consumer means consumption, and that’s definitely adding dependencies in response to data changes. Provider inherits the InheritedProvider. Provides the.of method, which is roughly the same as the Provider we wrote. At this point, we are done initializing the widgets and elements in the InhertiedProvider.

Summary, the Provider library increased _delegate and _InheritedProviderScopeMixin relative to us. During initialization, the need is exchanged. And our data, our wiretapping, it’s got to be related to both.

value & addListener

In the addListener debug and ChangeNotifierProvider. The value of the constructor in combination, can get the following code.

The static function _startListening exists in the ListenableProvider. _startListening takes the InheritedContext and Listenable parameters. The body of the function is

InheritedContext.markNeedsNotifyDependents

_startListening is passed as a parameter to _ValueInheritedProvider. We also notice that our generic T value is also given to the _ValueInheritedProvider, which is placed in the _delegate.

class ChangeNotifier{ @override void addListener(VoidCallback listener) { assert(_debugAssertNotDisposed()); _listeners.add(listener); } } /// class ListenableProvider{ ListenableProvider.value({ Key key, @required T value, UpdateShouldNotify<T> updateShouldNotify, Widget child, }) : super.value( key: key, value: value, updateShouldNotify: updateShouldNotify, startListening: _startListening, child: child, ); static VoidCallback _startListening(InheritedContext<Listenable> e, Listenable value,) { value? .addListener(e.markNeedsNotifyDependents);return() => value? .removeListener(e.markNeedsNotifyDependents); } } class InheritedProvider{ InheritedProvider.value({ Key key, @required T value, UpdateShouldNotify<T> updateShouldNotify, StartListening<T> startListening, bool lazy, Widget child, }) : _lazy = lazy, _delegate = _ValueInheritedProvider( value: value, updateShouldNotify: updateShouldNotify, startListening: startListening, ), super(key: key, child: child); } class _ValueInheritedProviderState<T> extends _DelegateState<T, _ValueInheritedProvider<T>> { VoidCallback _removeListener; @override T get value { element._isNotifyDependentsEnabled =false; _removeListener ?? = delegate.startListening? .call(element, delegate.value); element._isNotifyDependentsEnabled =true;
    returndelegate.value; }}Copy the code

And perform _startListening is in _ValueInheritedProviderState. Get the value. When is get Value called? Provider.of() takes the inheritedElement and retrieves the T value saved by its widget. Here is inheritedElement _DefaultInheritedProviderScopeElement. InheritedWidget _DefaultInheritedProviderScope. We see that the data is not stored directly in the Element or Widget, but rather in the _delegateState when initialized.

class Provider{
     static T of<T>(BuildContext context, {bool listen = true{...})return inheritedElement.value;
     }
}

class _DefaultInheritedProviderScopeElement{
    @override
    T get value => _delegateState.value;
}
Copy the code

In short, addListener is first called during Element Mount. You can see that the code remove is executed in element. unmount.

Once notifyListeners will call _DefaultInheritedProviderScopeElement markNeedsNotifyDependents

ChangeNotifier notifyListeners process

class CountModel extends ChangeNotifier {
  int count;
  CountModel(this.count);
  void increment() { count++; notifyListeners(); }}Copy the code

We entered the ChangeNotifier. NotifyListeners (), traverse and execute the registered listener.

class ChangeNotifier{
  @protected
  @visibleForTesting
  void notifyListeners() {
    if(_listeners ! = null) { final List<VoidCallback>localListeners = List<VoidCallback>.from(_listeners);
      for (VoidCallback listener in localListeners) {
          if(_listeners.contains(listener)) listener(); }}}}Copy the code

Enter the following

class _DefaultInheritedProviderScopeElement{
  @override
  void markNeedsNotifyDependents() {
    if(! _isNotifyDependentsEnabled)return;
    markNeedsBuild();
    _shouldNotifyDependents = true; }}Copy the code

We set _shouldNotifyDependents to true when addListener is added. So markNeedsBuild() will execute. We know from InheritedElement code analysis, the next step is _DefaultInheritedProviderScopeElement. PerformRebuild (), as well as the build ().

To review the InheritedElement, call the updateChild method –>updated >notifyClients–>notifyDependent.

The build() method is overridden, and notifyClients() is placed before super.build(). NotifyClients’ logic is defined by InheritedElement. The update of dependent is ok.

class _DefaultInheritedProviderScopeElement

@override
  Widget build() {
    if (_isLazy(widget) == false) {
      value; // this will force the value to be computed.
    }
    _delegateState.build(_isBuildFromExternalSources);
    _isBuildFromExternalSources = false;
    if (_shouldNotifyDependents) {
      _shouldNotifyDependents = false;
      notifyClients(widget);
    }
    return super.build();
  }
Copy the code

PerformRebuild () – > updateChild (). Since child.widget == newWidget returns child directly. Child.update () is no longer called.

class Element {
   @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if(child ! = null) {if (child.widget == newWidget) {
        if(child.slot ! = newSlot) updateSlotForChild(child, newSlot);return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        child.update(newWidget);
        returnchild; }}returninflateWidget(newWidget, newSlot); }}Copy the code

In our own simple version of ChangeNotifierProvider, after setState, a new InheritedWidget object is created each time state.build. So the child widgets! = newWidget, which goes to child(InheritedElement).update. InheritedElement is inherently not updating the elements underneath it. Instead, call notifyClients, update element that has dependencies.

From the analysis of the code, our perspective is always around Element. Widgets are used to build/update elements.

StreamProvider

The many subclasses of InheritedProvider are split into the following branches:

  • Provider
  • ListenableProvider/Proxy
    • ChangeNotifierProvider/Proxy *
  • DeferredInheritedProvider
    • ValueListenableProvider
    • FutureProvider
    • StreamProvider *

The Provider has no listening function. You need to manually manage listening. The Listenable branch, represented by The ChangeNotifierProvider, does not need to handle listening manually, but T needs to inherit Listenable. It’s the one we use the most. DeferredInheritedProvider. The Deferred branch does not listen on T directly. T has the following forms: Stream, Future, ValueListenable.

Flutter is a StreamBuilder for state management based on Stream. ChangeNotifierProvider already satisfies current usage scenarios, and StreamProvider is not used yet.