Antecedents feed

Around April, a wave of naked resignations. Since then, I have been playing games, reviewing and interviewing in a cycle, and so far there has not been a particularly satisfactory result.

I feel that I am beginning to develop towards the direction of Buddhism. Is this the great enlightenment after the ups and downs?

Just as a joke, the reason for this article is that in an interview, an interviewer asked me about the methods of cross-component communication in Flutter. One of the ways I said was to make a unified management so that cross-component communication could be achieved after global acquisition. However, the interviewer did not give me a positive feedback. So I decided to make a state management component like this. The next time someone asks me this question, I’ll tell them — “Let me tell you about a component I wrote (smile).”

Let’s get started

The refresh process of Flutter

If you want to create a state management component, you must first understand the Flutter refresh process. This process has already been understood in the source code of Flutter series, which will be briefly introduced below

  • callsetState()After, will correspond toElementAdded to theBuildOwnerMaintenance of the_dirtyElementsIn the list
  • Waiting for theengineframeA callback notification is triggeredWidgetsBindingdrawFrame()Method, and it’s going to iterate over the previous one_dirtyElements, according to theElementThe height in the tree is lowered from top to bottomrebuild()Method to recreate or update
  • ElementDuring the refresh process, you will need to relayout and paintRenderObjectStored in aPipelineOwnerMaintain the various lists, will be in laterRendererBindingdrawFrame()Methods in theRenderObjectLet’s have a unified update
  • When the refresh is complete, it is passedBuildOwnerfinalizeTree()To conduct a unified destruction operation

This is an overview of the refresh process. This process tells us that Flutter contains a list of objects that need to be updated or destroyed uniformly. Knowing this, it is clear that the state of Flutter can also be managed uniformly. This is the core principle of the state management component that will be implemented later.

InheritedElement and refresh

Before I introduce the state management component, I’d like to introduce the InheritedElement as a common guest. All the global theme modifications in Flutter are based on this object, and the corresponding InheritedWidget is the InheritedWidget. We can also communicate across components by using inheritedWidgets. But I personally always find it unsightly to use, so I rarely use it.

The popular provider library and its predecessor, Scope_model, are all built around InheritedElement, but there is a problem with using providers:

Provider. Of

(context) when you retrieve PageB’s Model from PageC, an error will be reported because the object is null.

[InheritedElement] [page stack] [InheritedElement] [page stack] [InheritedElement] [page stack]

The transmission of InheritedElement

The provider is commonly used in the provider of < T > (context) to obtain the corresponding data object, in the final call is BuildContext getElementForInheritedWidgetOfExactType method, its implementation is as follows

///Element Map<Type, InheritedElement> _inheritedWidgets; ///InheritedElement @override InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() { . final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];return ancestor;
  }
Copy the code

How does a lookup happen with _inheritedWidgets, and how does it happen with InheritedElement?

  ///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

This is done by copying the _inheritedWidgets of the parent node, as mentioned in Element in Source code

Now that we know how InheritedElement is passed and found, let’s look at another reason why providers can’t get objects

Page stack structure

We open and pop up a page using the Navigator, and eventually all the pages are encapsulated in the OverlayEntryWidget and added to the children list held by _Theatre. This means that all pages are actually horizontal in their data structure, as shown in a simple graph below

When InheritedElement is found, null is returned because PageC and PageB are flat. Obviously PageC cannot find the data corresponding to PageB (in fact, the corresponding Element is flat, which is simplified here).

This is where the problem with providers comes in, and the solution is simply to put all the Models into the GlobalModel and fetch them from the GlobalModel

This is a useful introduction to understanding state management, so how do I implement a state management component

Implement the state management component

The implementation idea is very simple, that is, by maintaining a HashMap object, put the corresponding Model of each page into it, and obtain it through the HashMap.

However, you might encounter the following scenario:

When you want to push multiple pages of the same type, there will be multiple Model objects of the same type. Obviously, you can’t get the specified Model by type in a HashMap. The solution is simply to maintain another HashMap with the key specified by the user. So you don’t have to worry about conflicts

That’s how it works, and the final code looks like this

class ModelWidget<T extends Model> extends StatefulWidget {
  final ChildBuilder<T> childBuilder;
  final ModelBuilder<T> modelBuilder;
  final String modelKey;

  const ModelWidget(
      {Key key,
      @required this.childBuilder,
      @required this.modelBuilder,
      this.modelKey})
      : super(key: key);

  @override
  _ModelWidgetState createState() => _ModelWidgetState<T>();
}

typedef ChildBuilder<T extends Model> = Widget Function(
    BuildContext context, T model);

typedef ModelBuilder<T extends Model> = T Function();

class _ModelWidgetState<T extends Model> extends State<ModelWidget<T>> {
    ...
}

class Model { ... }

class _StateDelegate { ... }

class ModelGroup {
  static Map<Type, Model> _map = new HashMap();
  static Map<String, Model> _repeatMap = new HashMap();

  static void _pushModel(Model model) => _map[model.runtimeType] = model;

  static void _pushModelWithKey(String key, Model model) =>
      _repeatMap[key] = model;

  static void _popModel(Model model) => _map.remove(model.runtimeType);

  static void _popModelWithKey(String key, Model model) => _repeatMap.remove(key);

  static T findModel<T extends Model>() => _map[T];

  static T findModelByKey<T extends Model>(String key) => _repeatMap[key];
}
Copy the code

Because the total amount of code is very small, interested in details of small partners can go directly to see the source code

Use as follows

🔑 Usage

Start by defining your Model object

class YourModel extends Model {
  @override
  void initState() {... } @override voiddispose() {... } int value = 0; }Copy the code

When you want to combine it with a Widget or page, you can do something like this

ModelWidget<YourModel>(
  childBuilder: (ctx, model) => YourWidgetOrPage(),
  modelBuilder: () => YourModel(),
),
Copy the code

🔄 Obtain data and refresh

To get the data

final model = ModelGroup.findModel<YourModel>();
Copy the code

The refresh

model.refresh();
Copy the code

You can also try this online demo by clicking on it

The last

In total, two components were open-source during naked resignation:

  • One is this recently completed Easy_model
  • Another is the rendering component for Markdown: markdown_widget (mainly for my personal Web blog written with Flutter)

In the meantime, a final announcement: underdog, online job search

If there is a good opportunity to promote, please do not let me go, my contact information is in the above blog address