preface

The design of MobX seems magical. It feels like using an Observer automatically keeps track of changes in state objects, enabling responsive refreshes. How exactly does this work? Let’s comb through the source code.

The Observer class

The class diagram for the Observer class is shown below.

There are several key classes that we will introduce.

StatelessObserverWidget:

abstract class StatelessObserverWidget extends StatelessWidget
    with ObserverWidgetMixin {
  /// Initializes [key], [context] and [name] for subclasses.
  const StatelessObserverWidget(
      {Key? key, ReactiveContext? context, String? name})
      : _name = name,
        _context = context,
        super(key: key);

  final String? _name;
  final ReactiveContext? _context;

  @override
  String getName() => _name ?? '$this';

  @override
  ReactiveContext getContext() => _context ?? super.getContext();

  @override
  StatelessObserverElement createElement() => StatelessObserverElement(this);
}
Copy the code

This createElement overrides the StatelessWidget method and returns a StatelessObserverElement object that is used to control the refresh of the Element.

ObserverWidgetMixin

This is a mixin for widgets whose main purpose is to create a reaction using the createReaction method so that the corresponding method can be invoked when the state changes. This createReaction is actually called in ObserverElementMixin.

mixin ObserverWidgetMixin on Widget {
  String getName();

  ReactiveContext getContext() => mainContext;

  @visibleForTesting
  Reaction createReaction(
    Function() onInvalidate, {
    Function(Object, Reaction)? onError,
  }) =>
      ReactionImpl(
        getContext(),
        onInvalidate,
        name: getName(),
        onError: onError,
      );

  void log(Stringmsg) { debugPrint(msg); }}Copy the code

StatelessObserverElement

StatelessObserverElement this is a special StatelessElement that can be described in the previous chapter on the rendering mechanism of Flutter: Introduction to Flutter This class is simply mixed in with ObserverElementMixin. All special businesses are implemented in ObserverElementMixin, so let’s take a look at the source of ObserverElementMixin.

mixin ObserverElementMixin on ComponentElement {
  ReactionImpl get reaction => _reaction;
  late ReactionImpl _reaction;

  // Not using the original `widget` getter as it would otherwise make the mixin
  // impossible to use
  ObserverWidgetMixin get _widget => widget as ObserverWidgetMixin;

  @override
  void mount(Element? parent, dynamic newSlot) {
    _reaction = _widget.createReaction(invalidate, onError: (e, _) {
      FlutterError.reportError(FlutterErrorDetails(
        library: 'flutter_mobx',
        exception: e,
        stack: e is Error ? e.stackTrace : null)); })as ReactionImpl;
    super.mount(parent, newSlot);
  }

  void invalidate() => markNeedsBuild();

  @override
  Widget build() {
    late Widget built;

    reaction.track(() {
      built = super.build();
    });

    if(! reaction.hasObservables) { _widget.log('No observables detected in the build method of ${reaction.name}',); }return built;
  }

  @override
  void unmount() {
    reaction.dispose();
    super.unmount(); }}Copy the code

As you can see, the mixin overrides the Elemennt mount method and creates a reaction inside the mount. The reaction method is invalidate, which is actually the markNeedsBuild method. This means that when the state data changes, the reaction call markNeedsBuild is used to tell Element to refresh, which actually triggers the Widget’s build method. This column has covered the markNeedsBuild method in previous chapters:

  • Learn more about State management with InheritedWidgets
  • Learn about State management from inheritedWidgets
  • What happened when analyzing setState from source code?

The build side is also overloaded in this mixin. The reaction track method is called. Following through, the main purpose here is to bind the Observer object to its dependencies (i.e. widgets returned by the Observer builder).

void _bindDependencies(Derivation derivation) {
    finalstaleObservables = derivation._observables.difference(derivation._newObservables!) ;finalnewObservables = derivation._newObservables! .difference(derivation._observables);var lowestNewDerivationState = DerivationState.upToDate;

    // Add newly found observables
    for (final observable in newObservables) {
      observable._addObserver(derivation);

      // Computed = Observable + Derivation
      if (observable is Computed) {
        if(observable._dependenciesState.index > lowestNewDerivationState.index) { lowestNewDerivationState = observable._dependenciesState; }}}// Remove previous observables
    for (final ob in staleObservables) {
      ob._removeObserver(derivation);
    }

    if(lowestNewDerivationState ! = DerivationState.upToDate) { derivation .. _dependenciesState = lowestNewDerivationState .. _onBecomeStale(); } derivation .. _observables = derivation._newObservables! . _newObservables = {};// No need for newObservables beyond this point
  }
Copy the code

This line is basically done, but how exactly is it tracked? Let’s take a look at the code generated by MobX.

State object tracking

The generated code for the member with the @Observable annotation is as follows:

final _$praiseCountAtom = Atom(name: 'ShareStoreBase.praiseCount');

@override
int get praiseCount {
  _$praiseCountAtom.reportRead();
  return super.praiseCount;
}

@override
set praiseCount(int value) {
  _$praiseCountAtom.reportWrite(value, super.praiseCount, () {
    super.praiseCount = value;
  });
}
Copy the code

The key here is that the Get method calls the reportRead method of the Atom class. The _reportObserved method actually ends up being called. This method simply associates the dependency of the previous Observer binding with the corresponding state object property. Therefore, when updating an attribute of a state object, only the components that depend on the attribute are updated to achieve accurate update.

void _reportObserved(Atom atom) {
  final derivation = _state.trackingDerivation;

  if(derivation ! =null) { derivation._newObservables! .add(atom);if(! atom._isBeingObserved) { atom .. _isBeingObserved =true. _notifyOnBecomeObserved(); }}}Copy the code

Now let’s look at the set method. The set method simply changes the properties of the state object, calling the reportWrite method of the Atom class. This triggers the following reaction scheduling method:

 void schedule() {
  if (_isScheduled) {
    return;
  }

  _isScheduled = true; _context .. addPendingReaction(this)
    ..runReactions();
}
Copy the code

The schedule method will eventually execute reaction’s _run method, which we see executing the _onInvalidate method that was passed in when createReaction was created in ObserverElementMixin, This method triggers the build of the Widget.

void _run() {
  if (_isDisposed) {
    return;
  }

  _context.startBatch();

  _isScheduled = false;

  if (_context._shouldCompute(this)) {
    try {
      _onInvalidate();
    } on Object catch (e, s) {
      // Note: "on Object" accounts for both Error and Exception_errorValue = MobXCaughtException(e, stackTrace: s); _reportException(_errorValue!) ; } } _context.endBatch(); }Copy the code

This shows how the state object is refreshed when it changes.

conclusion

Let’s track the entire process, and the actual MobX completes the imperceptive response as follows:

  • Controlled renderingElementStatelessObserverElementThe class inmountPhase throughcreateReactionregisteredreaction.
  • StatelessObserverElementinbuildIn the methodreactionobservableBind.
  • inObserverIs called when a state object property is read fromgetMethod that matches a state object property with the correspondingObserverComponent is bound.
  • When the properties of the state object aresetChanges are scheduled to the property bindingreaction, the implementation of_onInvalidateMethod to refresh, so as to achieve responsive perceptual refresh.

Of course, this is just our simple analysis, there are more details about the actual MobX implementation, and interested students can also get a deeper understanding of its design ideas.


I am island code farmers, wechat public account with the same name. This is a column about getting started with Flutter and its source code.

👍🏻 : feel the harvest please point a praise to encourage!

🌟 : Collect articles, easy to look back!

💬 : Comment exchange, mutual progress!