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
- call
setState()
After, will correspond toElement
Added to theBuildOwner
Maintenance of the_dirtyElements
In the list - Waiting for the
engine
的frame
A callback notification is triggeredWidgetsBinding
的drawFrame()
Method, and it’s going to iterate over the previous one_dirtyElements
, according to theElement
The height in the tree is lowered from top to bottomrebuild()
Method to recreate or update Element
During the refresh process, you will need to relayout and paintRenderObject
Stored in aPipelineOwner
Maintain the various lists, will be in laterRendererBinding
的drawFrame()
Methods in theRenderObject
Let’s have a unified update- When the refresh is complete, it is passed
BuildOwner
的finalizeTree()
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