Analysis of Flutter ScopedModel source

An article simple to understand a bit of usage ScopedModel,

This article we look at it in depth ScopedModel source principle.

In fact ScopedModel only one file, we directly open scoped_model.dartfiles,

From top to bottom.

Model

First at the top it is to Modellook at the source code:

abstract class Model extends Listenable {
  final Set<VoidCallback> _listeners = Set<VoidCallback>();
  int _version = 0;
  int _microtaskVersion = 0;

  /// [listener] will be invoked when the model changes.
  @override
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  /// [listener] will no longer be invoked when the model changes.
  @override
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }

  /// Returns the number of listeners listening to this model.
  int get listenerCount => _listeners.length;

  /// Should be called only by [Model] when the model has changed.
  @protected
  void notifyListeners() {
    // We schedule a microtask to debounce multiple changes that can occur
    // all at once.
    if (_microtaskVersion == _version) {
      _microtaskVersion++;
      scheduleMicrotask(() {
        _version++;
        _microtaskVersion = _version;

        // Convert the Set to a List before executing each listener. This
        // prevents errors that can arise if a listener removes itself during
        // invocation!
        _listeners.toList().forEach((VoidCallback listener) => listener());
      });
    }
  }
}
复制代码

Model inherited Listenable, as to why, see the back of it to understand.

The first two methods are easy to understand, add / remove a listener.

The latter notifyListeners()method,

The logic may lead to the most important is to _version ++, and eliminate concurrency change when the problem.

Logic that determines whether the first version and _microtaskVersion equal.

If they are equal, put the micro-task (_microtaskVersion) ++, and then start a micro-tasks to the version ++, and treatment listener ().

If you do not do the task is not equal, thus eliminating the problem may be caused because concurrency.

model Finder

This class is used to find Model, it has been abandoned in favor ScopedModel.of.

code show as below:

/// Finds a [Model]. Deprecated: Use [ScopedModel.of] instead.
@deprecated
class ModelFinder<T extends Model> {
  /// Returns the [Model] of type [T] of the closest ancestor [ScopedModel].
  ///
  /// [Widget]s who call [of] with a [rebuildOnChange] of true will be rebuilt
  /// whenever there's a change to the returned model.
  T of(BuildContext context, {bool rebuildOnChange: false}) {
    return ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange);
  }
}
复制代码

ScopedModel

This class is the focus of our viewing, after all, it is the name of the library.

Apart from anything else, first look at the class above comments:

/// Provides a [Model] to all descendants of this Widget.
///
/// Descendant Widgets can access the model by using the
/// [ScopedModelDescendant] Widget, which rebuilds each time the model changes,
/// or directly via the [ScopedModel.of] static method.
///
/// To provide a Model to all screens, place the [ScopedModel] Widget above the
/// [WidgetsApp] or [MaterialApp] in the Widget tree.


/// 向此小部件的所有后代提供[Model]。
/// 
/// 子代小部件可以使用[ScopedModelDescendant]小部件访问Model,该小部件在每次模型更改时重建,或者直接通过[ScopedModel.of]静态方法进行访问。
/// 
/// 要向所有页面提供Model,请将[ScopedModel]小部件放在小部件树中的[WidgetsApp]或[MaterialApp]上方。
复制代码

Notes written above it is clear that, if you want to all pages provide Model, then placed on top of the tree.

Look at the code:

class ScopedModel<T extends Model> extends StatelessWidget {
  /// The [Model] to provide to [child] and its descendants.
  final T model;

  /// The [Widget] the [model] will be available to.
  final Widget child;

  ScopedModel({@required this.model, @required this.child})
      : assert(model != null),
        assert(child != null);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: model,
      builder: (context, _) => _InheritedModel<T>(model: model, child: child),
    );
  }

  /// Finds a [Model] provided by a [ScopedModel] Widget.
  ///
  /// Generally, you'll use a [ScopedModelDescendant] to access a model in the
  /// Widget tree and rebuild when the model changes. However, if you would to
  /// access the model directly, you can use this function instead!
  static T of<T extends Model>(
    BuildContext context, {
    bool rebuildOnChange = false,
  }) {
    final Type type = _type<_InheritedModel<T>>();

    Widget widget = rebuildOnChange
        ? context.inheritFromWidgetOfExactType(type)
        : context.ancestorWidgetOfExactType(type);

    if (widget == null) {
      throw new ScopedModelError();
    } else {
      return (widget as _InheritedModel<T>).model;
    }
  }

  static Type _type<T>() => T;
}
复制代码

First, ScopedModel need to confirm a generic type Model, and is a stateless widget.

Then ScopedModel need a child Widget, this child Widget can be said of our MaterialApp.

Next is the build()method, which is ScopedModel very essence of place.

build () method

build () method used AnimatedBuilderto build the widget, because AnimatedBuilderautomatically monitor change animation data.

AnimatedBuilderThe animation parameters is needed is a Listenable objects, and our Model just inherited is Listenable.

So we have to manually place a custom Model, update the call notifyListeners().

notifyListeners()Also I said earlier, is to _version ++.

Since _version ++, and then we want to achieve the purpose of updating the data using the UI.

of () method

of () method we are very familiar with, is used to get the Model.

Very simple to use up, but there was a lot of content, look at the source code:

static T of<T extends Model>(
  BuildContext context, {
    bool rebuildOnChange = false,
  }) {
  final Type type = _type<_InheritedModel<T>>();

  Widget widget = rebuildOnChange
    ? context.inheritFromWidgetOfExactType(type)
    : context.ancestorWidgetOfExactType(type);

  if (widget == null) {
    throw new ScopedModelError();
  } else {
    return (widget as _InheritedModel<T>).model;
  }
}

static Type _type<T>() => T;
复制代码

of () method of the need to pass a generic Model,

First, get a bit of its Type, followed by rebuildOnChange depending on the value of inheritFromWidgetOfExactType/ ancestorWidgetOfExactTypeto find the widget.

Here to explain these two methods:

inheritFromWidgetOfExactType It is used to get to the nearest widget given type, and automatically re-constructed when the updated values.

ancestorWidgetOfExactType It is used to obtain a given type recent ancestor Widget, and does not rebuild when the updated values.

So this brought under control UI updates are not necessary.

_InheritedModel

And build of the above methods, have emerged this Model,

He is actually a InheritedWidgetrewrite of updateShouldNotifymethods to control redraw.

code show as below:

class _InheritedModel<T extends Model> extends InheritedWidget {
  final T model;
  final int version;

  _InheritedModel({Key key, Widget child, T model})
      : this.model = model,
        this.version = model._version,
        super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
      (oldWidget.version != version);
}
复制代码

The logic is redrawn version are the same.

ScopedModelDescendant

This class is a packaged ScopedModel.ofmethod of stateless widget.

code show as below:

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
  /// Called whenever the [Model] changes.
  final ScopedModelDescendantBuilder<T> builder;

  /// An optional constant child that does not depend on the model.  This will
  /// be passed as the child of [builder].
  final Widget child;

  /// An optional constant that determines whether the
  final bool rebuildOnChange;

  /// Constructor.
  ScopedModelDescendant({
    @required this.builder,
    this.child,
    this.rebuildOnChange = true,
  });

  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      child,
      ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
    );
  }
}
复制代码

DETAILED not elaborate, because there is little logic in it.

to sum up

ScopedModel can see the design is really clever,

Use AnimatedBuilder and InheritWidget to do global state management.

About micro-task (Microtask) and EventQueue understanding, I will continue the article is about.

Model how to use it more? Use mixin it.

Reproduced in: https: //juejin.im/post/5d00ca4af265da1b6d401de5

Guess you like

Origin blog.csdn.net/weixin_33736048/article/details/93164654