review

Last time, we had a global understanding of Flutter State Management and learned how to distinguish good from bad. It is ok not to know this. We will analyze the situation of Flutter State Management in more detail by reading the source code of Flutter Provider. Let’s take a look at how the framework is organized and how it helps us.

The current content

Officially, we know that a Provider is a wrapper around the InheritedWidget to make the InheritedWidget easier to use and highly reusable. We also know that it has some disadvantages such as

  • Easy to cause unnecessary refresh
  • State across pages (route) is not supported, meaning across trees. If it is not in a tree, we cannot get it
  • Data is immutable and must be used in conjunction with StatefulWidget, ChangeNotifier, or Steam

I would like to know how to avoid these disadvantages in the design of the Provider. Another is that the Stream does not actively close the drain channel and has to be used in conjunction with the StatefulWidget. The Provider provides the Dispose callback, which you can close in this function. How do you do that? With these questions, we go looking for answers

How to use

Let’s use it first, and then analyze the source code based on the use case to find the answer we want. Let’s look at a simple example

step 1

The first step is to define a ChangeNotifier, which is responsible for data change notification

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() { _count++; notifyListeners(); }}Copy the code

step 2

Step 2: Use ChangeNotifierProvider to subscribe to Counter. The ChangeNotifierProvider is a wrapper around the InheritedWidget that shares the state of the Counter with the child widgets. I’ve put the ChangeNotifierProvider in my Main function, and it’s at the top of the Widget tree, and of course this is a simple example, so I don’t have a problem writing it this way, but you have to think, if it’s a very local state, Please place the ChangeNotifierProvider locally and not globally, hopefully you understand what I mean

void main() {
  runApp(
    /// Providers are above [MyApp] instead of inside it, so that tests
    /// can use [MyApp] while mocking the providers
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: MyApp(),
    ),
  );
}
Copy the code

step 3

The third step is to receive the data through the Consumer, which is the Consumer responsible for consuming the data produced by the ChangeNotifierProvider

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('MyHomePage build');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Example'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),

            /// Extracted as a separate widget for performance optimization.
            /// As a separate widget, it will rebuild independently from [MyHomePage].
            ///
            /// This is totally optional (and rarely needed).
            /// Similarly, we could also use [Consumer] or [Selector].
            Consumer<Counter>(
              builder: (BuildContext context, Counter value, Widget child) {
                return Text('${value.count}'); }, ), OtherWidget(), const OtherWidget2() ], ), ), floatingActionButton: FloatingActionButton( /// Calls `context.read` instead of `context.watch` so that it does not rebuild /// when [Counter]  changes. onPressed: () => context.read<Counter>().increment(), tooltip:'Increment', child: const Icon(Icons.add), ), ); }}Copy the code

From this example, you can see that the Provider package is easy to use and that Counter uses with ChangeNotifier instead of extends as the Model layer, so it’s not too intrusive. So does InheritedWidget’s downside get circumvented?

  1. Easy to cause unnecessary refreshes (solved?)

The OtherWidget does nothing to subscribe to Counter. The OtherWidget2 listens through context.watch(). Count instead of Consumer. Then add print to both build functions

class OtherWidget extends StatelessWidget {
  const OtherWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('OtherWidget build');
//    Provider.of<Counter>(context);
    return Text(
        /// Calls `context.watch` to make [MyHomePage] rebuild when [Counter] changes.
        'OtherWidget',
        style: Theme.of(context).textTheme.headline4);
  }
}

class OtherWidget2 extends StatelessWidget {
  const OtherWidget2({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('OtherWidget2 build');
    return Text(
      /// Calls `context.watch` to make [MyHomePage] rebuild when [Counter] changes.
        '${context.watch<Counter>().count}', style: Theme.of(context).textTheme.headline4); }}Copy the code

The project runs and if you look at the effect, it runs like this

  • Both Consumer and Context. watch can listen for Counter changes
  • The Consumer just refreshes itself
  • The child widget where context.watch is located, whether const or not, is rebuilt and refreshed
  • OtherWidget is not rebuilt because it does not subscribe to Counter

The second problem does not support cross-page (route) state, which is definitely not supported. The third problem is that data is immutable (read only). You can tell from this example that data is really mutable, right? Leave a suspense, the following analysis of the source code to see the essence.

Of course, for a more complete understanding of the relationship between ChangeNotifier, ChangeNotifierProvider, and Consumer, see the figure below

Source code analysis

ChangeNotifier

Dart is the code for the Flutter SDK, which is not part of the Provider framework. This is a standard observer model. Typedefs VoidCallback = void Function(); UI is a function defined under the dart. UI package that does not return any arguments. ChangerNotifier implements an abstract class named Listenable. Listenable is an abstract class that maintains a listener list.

ChangeNotifierProvider

class ChangeNotifierProvider<T extends ChangeNotifier> extends ListenableProvider<T> { static void _dispose(BuildContext  context, ChangeNotifier notifier) { notifier? .dispose(); } // use 'create' to create a [ChangeNotifier] // Automatically unsubscribe when ChangeNotifierProvider is removed from the tree via /// notifier? .dispose(); ChangeNotifierProvider({ Key key, @required Create<T> create, bool lazy, TransitionBuilder builder, Widget child, }) : super( key: key, create: create, dispose: _dispose, lazy: lazy, builder: builder, child: child, ); / / / generates an existing ChangeNotifier Provider ChangeNotifierProvider. Value ({Key Key, @ required T value, TransitionBuilder builder, Widget child, }) : super.value( key: key, builder: builder, value: value, child: child, ); }Copy the code

Analyze the structure

  • Create Create is a general Function typedef Create = T Function(BuildContext context) is used to Create the T class, which is responsible for creating the ChangeNotifier
  • Bool lazy Indicates whether lazy loading is performed
  • Child Widget will not be used as a child Widget when builder exists, as can be seen in the following figure
  • Use child if Widget Child Builder does not exist

Inherit from ListenableProvider to continue analyzing its source code

Class ListenableProvider<T extends Listenable> extends InheritedProvider<T> {/// Use [create] to create a [Listenable] subscription to it /// [create] cannot be empty ListenableProvider({Key Key, @required Create<T> create, Dispose<T> dispose, bool lazy, TransitionBuilder builder, Widget child, }) : assert(create ! = null), super( key: key, startListening: _startListening, create: create, dispose: dispose, debugCheckInvalidValueType: kReleaseMode ? null : (value) {if(value is ChangeNotifier) { // ignore: invalid_use_of_protected_member ... } }, lazy: lazy, builder: builder, child: child, ); Listenableprovider. value({Key Key, @required T value, UpdateShouldNotify<T> updateShouldNotify, TransitionBuilder builder, Widget child, }) : super.value( key: key, builder: builder, 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);
  }
}
Copy the code
  • Listenable, as analyzed above, is the abstraction responsible for managing the list of observers
  • Dispose = void Function(BuildContext context, T value); dispose = void Function(BuildContext context, T value); This is a callback that should be triggered when the page is destroyed.

[InheritedProvider] [InheritedProvider] [InheritedProvider] [InheritedProvider] don’t give up

Class InheritedProvider < T > extends SingleChildStatelessWidget {/ / / to create value and share the data to the child widgets / / / when [InheritedProvider] When released from the tree, Value InheritedProvider({Key Key, Create<T> Create, T update(BuildContext context, T value), UpdateShouldNotify<T> updateShouldNotify, void Function(T value) debugCheckInvalidValueType, StartListening<T> startListening, Dispose<T> dispose, TransitionBuilder builder, bool lazy, Widget child, }) : _lazy = lazy, _builder = builder, _delegate = _CreateInheritedProvider( create: create, update: update, updateShouldNotify: updateShouldNotify, debugCheckInvalidValueType: debugCheckInvalidValueType, startListening: startListening, dispose: dispose, ), super(key: key, child: child); Value inheritedProvider. value({Key Key, @required T value, UpdateShouldNotify<T> UpdateShouldNotify, StartListening<T> startListening, bool lazy, TransitionBuilder builder, Widget child, }) : _lazy = lazy, _builder = builder, _delegate = _ValueInheritedProvider( value: value, updateShouldNotify: updateShouldNotify, startListening: startListening, ), super(key: key, child: child); InheritedProvider._constructor({ Key key, _Delegate<T> delegate, bool lazy, TransitionBuilder builder, Widget child, }) : _lazy = lazy, _builder = builder, _delegate = delegate, super(key: key, child: child); final _Delegate<T> _delegate; final bool _lazy; final TransitionBuilder _builder; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); _delegate.debugFillProperties(properties); } @override _InheritedProviderElement<T>createElement() {
    return_InheritedProviderElement<T>(this); } @override Widget buildWithChild(BuildContext context, Widget child) { assert( _builder ! = null || child ! = null,'$runtimeType used outside of MultiProvider must specify a child',);return _InheritedProviderScope<T>(
      owner: this,
      child: _builder != null
          ? Builder(
              builder: (context) => _builder(context, child),
            )
          : child,
    );
  }
}
Copy the code

The extra parameters in the construction

  • T update(BuildContext context, T value) This function returns the value of a change in data, implemented in the _CreateInheritedProvider class. So it must depend on someone else to change the state, and it creates a _CreateInheritedProvider class, which is an implementation of _Delegate, which is a delegate class for the state, Let’s take a look at the _Delegate implementation
@immutable
abstract class _Delegate<T> {
  _DelegateState<T, _Delegate<T>> createState();

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

abstract class _DelegateState<T, D extends _Delegate<T>> {
  _InheritedProviderScopeElement<T> element;

  T get value;

  D get delegate => element.widget.owner._delegate as D;

  bool get hasValue;

  bool debugSetInheritedLock(bool value) {
    return element._debugSetInheritedLock(value);
  }

  bool willUpdateDelegate(D newDelegate) => false;

  void dispose() {}

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}

  void build(bool isBuildFromExternalSources) {}
}
Copy the code

This is done using delegate pattern, which is similar to the StatefulWidget/State relationship. The same _DelegateState provides life-cycle functions such as willUpdateDelegate to update a new delegate, Dispose dispose, etc

  • UpdateShouldNotify updateShouldNotify, void Function(T value) debugCheckInvalidValueType, StartListening StartListening, Dispose Dispose, these are all given to the delegate class
  • The key implementation is coming, we haven’t seen the InheritedWidget logic so far, right? It comes with Widget buildWithChild(BuildContext Context, Widget Child), The Widget we passed in is wrapped with a class called _InheritedProviderScope
class _InheritedProviderScope<T> extends InheritedWidget {
  _InheritedProviderScope({
    this.owner,
    @required Widget child,
  }) : super(child: child);

  final InheritedProvider<T> owner;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

  @override
  _InheritedProviderScopeElement<T> createElement() {
    return_InheritedProviderScopeElement<T>(this); }}Copy the code

Do you notice that all the functions are taken away by _Delegate, leaving the widgets to be given to _InheritedProviderScope? This is a good design, because all the inheritedWidgets can do is share data. It doesn’t have anything to do with the function. The only thing that matters, I guess, is that it’s called in the Widget provided by the InheritedWidget

Owner: This passes the InheritedProvider itself to the InheritedWidget in the buildWithChild function, presumably to facilitate calls to its _Delegate delegate class.

. Almost one o ‘clock. Go to bed. More tomorrow

Continue to share, _InheritedProviderScope the only special place, we found its own created a Element to realize by covering the createElement method function, return _InheritedProviderScopeElement instance, The framework implements a layer of elements on its own. We all know that widgets are configuration files only for build and rebuild and remove from the tree. Virtual Dom Element as a layer, is mainly responsible for optimization, optimization of the logic of a page refresh, that let us have a detailed analysis of _InheritedProviderScopeElement, see it all done?

// InheritedContext InheritedContext InheritedContext InheritedContext More than a T paradigm class _InheritedProviderScopeElement < T > extends InheritedElement implements InheritedContext < T > {/ / / constructor, The Element the corresponding widget incoming _InheritedProviderScopeElement (_InheritedProviderScope < T > widgets) : super (widget); /// Whether to notify dependent Element changes bool _shouldNotifyDependents =false; / / / is it allowed to inform change bool _isNotifyDependentsEnabled =true; // first build bool _firstBuild =true; // Whether to update the newWidget's Delegate Delegate bool _updatedShouldNotify =false; /// This variable is the controlled data change and is set to Widget changes and Element dependent changestrue
  bool _isBuildFromExternalSources = false; _DelegateState<T, _Delegate<T>> _DelegateState; @override _InheritedProviderScope<T> get widget => super.widget as _InheritedProviderScope<T>; @override void updateDependencies(Element dependent, Object aspect) { final dependencies = getDependencies(dependent); // once subscribed to everything once, it always stays subscribed to everythingif(dependencies ! = null && dependencies is! _Dependency<T>) {return;
    }

    if (aspect is _SelectorAspect<T>) {
      final selectorDependency =
          (dependencies ?? _Dependency<T>()) as _Dependency<T>;

      if (selectorDependency.shouldClearSelectors) {
        selectorDependency.shouldClearSelectors = false;
        selectorDependency.selectors.clear();
      }
      if (selectorDependency.shouldClearMutationScheduled == false) {
        selectorDependency.shouldClearMutationScheduled = true; SchedulerBinding.instance.addPostFrameCallback((_) { selectorDependency .. shouldClearMutationScheduled =false
            ..shouldClearSelectors = true;
        });
      }
      selectorDependency.selectors.add(aspect);
      setDependencies(dependent, selectorDependency);
    } else {
      // subscribes to everything
      setDependencies(dependent, const Object());
    }
  }

  @override
  void notifyDependent(InheritedWidget oldWidget, Element dependent) {
    final dependencies = getDependencies(dependent);

    var shouldNotify = false;
    if(dependencies ! = null) {if (dependencies is _Dependency<T>) {
        for (final updateShouldNotify in dependencies.selectors) {
          try {
            assert(() {
              _debugIsSelecting = true;
              return true; } ()); shouldNotify = updateShouldNotify(value); } finally { assert(() { _debugIsSelecting =false;
              return true; } ()); }if (shouldNotify) {
            break; }}}else {
        shouldNotify = true; }}if (shouldNotify) {
      dependent.didChangeDependencies();
    }
  }

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false; _delegateState = widget.owner._delegate.createState().. element = this; } super.performRebuild(); } @override void update(_InheritedProviderScope<T> newWidget) { _isBuildFromExternalSources =true;
    _updatedShouldNotify =
        _delegateState.willUpdateDelegate(newWidget.owner._delegate);
    super.update(newWidget);
    _updatedShouldNotify = false;
  }

  @override
  void updated(InheritedWidget oldWidget) {
    super.updated(oldWidget);
    if (_updatedShouldNotify) {
      notifyClients(oldWidget);
    }
  }

  @override
  void didChangeDependencies() {
    _isBuildFromExternalSources = true;
    super.didChangeDependencies();
  }

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

  @override
  void unmount() {
    _delegateState.dispose();
    super.unmount();
  }

  @override
  bool get hasValue => _delegateState.hasValue;

  @override
  void markNeedsNotifyDependents() {
    if(! _isNotifyDependentsEnabled)return;

    markNeedsBuild();
    _shouldNotifyDependents = true;
  }

  @override
  T get value => _delegateState.value;

  @override
  InheritedWidget dependOnInheritedElement(
    InheritedElement ancestor, {
    Object aspect,
  }) {
    returnsuper.dependOnInheritedElement(ancestor, aspect: aspect); }}Copy the code
  • Void update(_InheritedProviderScope newWidget) lets the page rebuild here, because InheritedElement inherits from ProxyElement, In ProxyElement, the update function calls two updated functions, and the rebuild function triggers the rebuild logic. Here is the code traced
abstract class ProxyElement extends ComponentElement { @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(); }}Copy the code
  • PerformRebuild () is called after the update triggers the actual call to rebuild
  • UpdateDependencies and notifyDependent handle Element dependency logic
  • Update, updated Handles widget update logic
  • DidChangeDependencies Called when a dependency of this State object changes. Subclasses rarely override this method because the framework always calls Build after a dependency changes. Some subclasses do override this approach because they need to do some expensive work (for example, network fetching) when their dependencies change, and it will be too expensive for each build.
  • Build () builds the required widget, and Element triggers the widget’s build when it calls build
  • Void unmount() see _delegatestate.dispose (); Dispose function is disposed when an Element is removed from the tree.

Take a look at a life cycle diagram to help you understand the source code invocation relationship

Reactive

  • What does notifyClients do? It is implemented in InheritedElement function, learned from the official document, it is by calling Element. Inform all subordinate Element didChangeDependencies this inherited widget has been changed, this method can only be called during the construction phase, usually, This method is called automatically when inherited Widgets are rebuilt, and when InheritedNotifier, which is a subclass of inheritedWidgets, is called when its Listenable sends notifications.

  • MarkNeedsNotifyDependents if you call it, will inform all depend on the Element refresh after being forced to build the widget, look at the following code, and found that the function defined in InheritedContext, So we can use the InheritedContext context to force the build of the page

Abstract Class InheritedContext<T> extends BuildContext {/// [InheritedProvider] currently shares data /// this property is lazy-loaded, The first time you read it might trigger some side effects, T get value; / / / will be/InheritedProvider marked as need to update the dependencies / / / bypass [InheritedWidget. UpdateShouldNotify] and will be forced to rebuild the void markNeedsNotifyDependents(); ///setIf State has been called at least once /// [DeferredStartListening] can be used to distinguish between the first listen and the rebuild after "controller" changes. bool get hasValue; }Copy the code

To summarize, let’s review how we use InheritedWidget. In order for InheritedWidget’s children to refresh, we have to rely on Statefulwidget and refresh Element with State control. When we call setState to refresh the page, we call the _element.markNeedsBuild() function. Element controls the page, so Provider encapsulates _delegateState. Is private, not public use to us, also did not provide similar setState, but can be achieved through markNeedsNotifyDependents function and the same call setState effect, the same is to let all child widgets to rebuild, but we want local refresh? Is it in Consumer? Come on, don’t go away, no ads, wonderful continue, next research Consumer source code

Consumer

The class Consumer < T > extends SingleChildStatelessWidget {/ / / constructor, Builder Consumer({Key Key, @required this.builder, Widget child,}) : Assert (Builder! = null), super(key: key, child: child); Final Widget Function(BuildContext Context, T value, Widget Child) Builder; @override Widget buildWithChild(BuildContext context, Widget child) {returnbuilder( context, Provider.of<T>(context), child, ); }}Copy the code
  • Here the source code, a bit of a child to the parent Widget SingleChildStatelessWidget, eventually child passed back through buildWithChild function parameters, and builder function has received the child, Then combine the Child and the widget that needs to be refreshed to combine a new widget to the Consumer. If the Child is not empty, then you need to organize the relationship between the child and the returned widget in the Builder.
  • Provider. Of (context) obtains the shared data value

How does Provider. Of (context) get data? Keep looking at the source code

/// call the _inheritedElementOf function static T of<T>(BuildContext context, {bool listen =true}) { assert(context ! = null); final inheritedElement = _inheritedElementOf<T>(context);if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }

    return inheritedElement.value;
  }

static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(
      BuildContext context) {
  
    _InheritedProviderScopeElement<T> inheritedElement;

    if (context.widget is _InheritedProviderScope<T>) {
      // An InheritedProvider<T>'s update tries to obtain a parent provider of // the same type. context.visitAncestorElements((parent) { inheritedElement = parent.getElementForInheritedWidgetOfExactType< _InheritedProviderScope
      
       >() as _InheritedProviderScopeElement
       
        ; return false; }); } else { inheritedElement = context.getElementForInheritedWidgetOfExactType< _InheritedProviderScope
        
         >() as _InheritedProviderScopeElement
         
          ; } if (inheritedElement == null) { throw ProviderNotFoundException(T, context.widget.runtimeType); } return inheritedElement; }
         
        
       
      Copy the code
  • Through visitAncestorElements to parent find _InheritedProviderScope implementation class is InheritedWidget, when find is just returned _InheritedProviderScopeElement, And _InheritedProviderScopeElement just can get the value, the value is _delegateState value
@override
  T get value => _delegateState.value;
Copy the code

All I’m doing here is reading the data, so how does the data get refreshed? Let’s go back to the next few pieces of code

  1. Model data calls the function notifyListeners provided by ChangeNotifier
  void notifyListeners() {
    assert(_debugAssertNotDisposed());
    if(_listeners ! = null) { final List<VoidCallback>localListeners = List<VoidCallback>.from(_listeners);
      for (final VoidCallback listener in localListeners) {
        try {
          if (_listeners.contains(listener))
            listener();
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'foundation library',
            context: ErrorDescription('while dispatching notifications for $runtimeType'),
            informationCollector: () sync* {
              yield DiagnosticsProperty<ChangeNotifier>(
                'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ); })); }}}}Copy the code

VoidCallback (VoidCallback, VoidCallback, VoidCallback, VoidCallback)

  1. In the static function of the ChangeNotifierProvider parent class ListenableProvider, it automatically subscribs to the observer and as I said observer is a normal function, And e.m arkNeedsNotifyDependents InheritedContext is a function, it is when you notifyListeners execution markNeedsNotifyDependents, We know markNeedsNotifyDependents above similar setState effect, thus to realize the UI refresh.
Static VoidCallback _startListening(InheritedContext<Listenable> e, Listenable value) ) { value? .addListener(e.markNeedsNotifyDependents); /// Add an observerreturn() => value? .removeListener(e.markNeedsNotifyDependents); } class InheritedContext<T> extends BuildContext {... void markNeedsNotifyDependents(); . }Copy the code

Local refresh to this location is not unveiled? How does it work? Look for it with me. First let’s look at something

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }
Copy the code

The Consumer is listening to the data by saying provider.of (context), right, and that’s all we’re refreshing, so if we look at its implementation we see another detail

  static T of<T>(BuildContext context, {bool listen = true}) { assert(context ! = null); final inheritedElement = _inheritedElementOf<T>(context);if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }
    return inheritedElement.value;
  }
Copy the code

This calls the BuildContext dependOnInheritedElement function. What does this function do?

 @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    ...
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
Copy the code
 @override
  void updateDependencies(Element dependent, Object aspect) {
    print("updateDependencies===================dependent ${dependent.toString()}"); final dependencies = getDependencies(dependent); .setDependencies(dependent, const Object()); . }Copy the code
  ///    to manage dependency values.
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }
Copy the code
  final Map<Element, Object> _dependents = HashMap<Element, Object>();
Copy the code

Trigger updateDependencies, and use setDependencies to cache Element in the _dependents Map

Finally, it is updated with the following code

 @override
  void notifyDependent(InheritedWidget oldWidget, Element dependent) {
    print("notifyDependent===================oldWidget ${oldWidget.toString()}");
    final dependencies = getDependencies(dependent);

    var shouldNotify = false;
    if(dependencies ! = null) {if (dependencies is _Dependency<T>) {
        for (final updateShouldNotify in dependencies.selectors) {
          try {
            assert(() {
              _debugIsSelecting = true;
              return true; } ()); shouldNotify = updateShouldNotify(value); } finally { assert(() { _debugIsSelecting =false;
              return true; } ()); }if (shouldNotify) {
            break; }}}else {
        shouldNotify = true; }}if(shouldNotify) { dependent.didChangeDependencies(); // update method}}Copy the code

NotifyListeners trigger the performRebuild of the InheritedWidget, build, and notifyClients. NotifyClients triggers notifyDependent, which obtains the cached Element from getDependencies. Eventually determine whether need to refresh and then call dependent. DidChangeDependencies (); If a widget is subscribed to by provider. of, it is cached in a Map by InheritedWidget. If the child widget is not in the cached Map, it is not refreshed at all. And if the shouldNotify variable is false it will not refresh. This control must be even more fine-grained if the child Widget is subscribed but does not refresh itself.

Source code analysis summary

Now understand

  • The Provider implements a local refresh by caching the inheritedElement
  • Update the UI by controlling the Element layer you implement
  • Dispose is called back by the unmount function provided by Element to achieve selective release

Severe? Not bad.

The tip of the iceberg

So far, I have only analyzed ChangeNotifierProvider and Consumer. In fact, there are many more. Here is a picture to scare you

This is a big picture. Please look at the original one. Do you think it’s the tip of the iceberg? [InheritedProvider] [InheritedProvider] [InheritedProvider] [InheritedProvider] [InheritedProvider] [InheritedProvider] [InheritedProvider]

conclusion

Do you have any favorite Flutter state management framework? If you want to see more source code analysis of Flutter state management framework, please follow me. If you get to the end of this article and you think it’s good, please give me a thumbs up too