background
Native StatefulWidget provides the stateful component to manage the state. For multi-component state interaction, the parent component can be used to manage and distribute the state. However, once the business is complex and there are enough branches in the component tree, the state will sink deeply and the state transfer will be complicated.
The simple case looks like this:
As you add functionality, your application will have dozens or even hundreds of states. Your application should look something like this:
The above is actually the scenario where multiple pages need to share state and transfer information. The direct approach is as follows:
-
Distributing notifications through the parent widget has a deep nesting level problem, and the setState of the parent level causes unnecessary build problems
-
Through callback pass, there is also the problem of deep pass, callback will also miss the call problem
Problems faced
-
How to get a data source
-
How to update the data source
-
How do I notify components of data source updates
-
How are data sources shared across components
Data flow concept
State management will be based on one-way data flow, here is the concept of data flow
Unidirectional data flow
-
State: Data source that drives the application.
-
View: map state to the view declaratively.
-
Actions: Changes in state in response to user input on the view
State management of one-way data flows: By defining and isolating concepts in state management and enforcing compliance with certain rules, our code becomes more structured and maintainable
Features: (1) All state changes can be recorded, traceable, easy to trace the source; (2) There is only one copy of all data, and only one entrance and exit of component data, which makes the program more intuitive and easier to understand and conducive to the maintainability of the application; (3) Once the data changes, to update the page (data-> page), but did not (page ->data); (4) If users make changes on the page, it will be collected manually (two-way automatic) and merged into the original data
Two-way data flow
Bidirectional data binding brings bidirectional data flow, bidirectional binding between data (state) and View (View).
Ng-model for NG and V-Model for VUE, and DataBinding for Android
What it boils down to is a syntactic sugar (one-way binding of value + onChange event listening)
Features: (1) No matter the data changes, or user operations, can bring mutual changes, automatic update. Applies to project details, such as UI controls (usually form-like operations). (2) The change of state is uncontrollable
The solution
StatefulWidget
The official stateful component can manage various states in its component by itself or by the parent component. Which management is appropriate? Generally, the following principles are followed:
- If the state is user data, such as the checked status of a checkbox, or the position of a slider, that state is best managed by the parent Widget.
- If the state is about the appearance of the interface, such as color or animation, then the state is best managed by the Widget itself.
- If a state is shared by different widgets, it is best managed by their common parent Widget.
In general, it is better to manage state encapsulation inside the Widget, while managing state in the parent Widget is more flexible. If you do not know whether it should be managed by the Widget itself, design it as a StatelessWidget in preference to the parent Widget
Question:
In the case of single function and complex multiple pages, state sinking is too deep, state transfer is complex, and the rebuild scope is too large
InherityWidget
Functional components provide a mechanism for data to be passed from top to bottom in the widget tree and shared
For example, if a state is shared in the root Widget with an InherityWidget, it can be acquired in any of the child widgets. Theme,Navigator, and so on are shared across the entire app using this mechanism, eliminating the need for cascading
use
- A parent Widget used to store shared data that inherits the InheritedWidget
class FatherWidget extends InheritedWidget { final int data; FatherWidget({@required this.data, Widget child}) : super(child: child); Static FatherWidget of(BuildContext context) {returncontext.inheritFromWidgetOfExactType(FatherWidget); Override bool updateShouldNotify(FatherWidget oldWidget) {// Override bool updateShouldNotify(FatherWidget oldWidget) {return oldWidget.data != data;
}
}
Copy the code
-
Child widgets that get state and handle responses when dependencies change
class ChildWidget extends StatefulWidget { @override _ChildWidgetState createState() => _ChildWidgetState(); } class _ChildWidgetState extends State<ChildWidget> { @override Widget build(BuildContext context) { return new Text(FatherWidget.of(context).data.toString()); } @override void didChangeDependencies() { super.didChangeDependencies(); // The InheritedWidget in the parent or ancestor widget changes (updateShouldNotify returnstrue// If there is no InheritedWidget in the build, this callback will not be calledprint("didChangeDependencies = "+ FatherWidget.of(context).data.toString()); }}Copy the code
-
integration
class ContainerWidget extends StatefulWidget { @override _ContainerWidgetState createState() => _ContainerWidgetState(); } class _ContainerWidgetState extends State<ContainerWidget> { int _data = 0; void_incrementCounter() { setState(() { _data++; /// change the state}); } @override Widget build(BuildContext context) {returnFatherWidget( data: widget.data, child: ChildWidget(), ); }}Copy the code
graphic
- How to implement a subtree to get an InheritedWidget
- How do inheritedWidgets connect with child widgets that get it with “of”
The key point
“DidChangeDependencies” :
One of the lifecycle functions in State that is notified by a Framework call when a dependency changes, in this case whether the child Widget uses the InherityWidget’s data
In addition, this callback is executed immediately after initState, which can be used by retrieving.context directly
The source code
How to implement a subtree to get an InheritedWidget directly
When we create a build InheritedWidget, the corresponding Element calls the following _updateInheritance method: Copy _inheritedWidgets from the parent Element and register this InheritedElement into _inheritedWidgets
class InheritedElement extends ProxyElement { final Map<Element, Object> _dependents = HashMap<Element, Object>(); . @override void_updateInheritance() {
...
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
The acquisition process is as follows
Call the of method static ThemeData of(BuildContext Context, {bool shadowThemeOnly =false}) { final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme); . } /// Element class @override InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) { ... final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; /// Take [InheritedElement] by typeif(ancestor ! = null) { assert(ancestor is InheritedElement);returninheritFromElement(ancestor, aspect: aspect); / / / a child Element and InheritedElement relationship here and return InheritedWidget} _hadUnsatisfiedDependencies =true;
returnnull; } @override InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) { ... _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);return ancestor.widget;
}
Copy the code
Note: There is also a way to only take an InheritedElement without registering the dependencies
/// Element class
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
...
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
return ancestor;
}
Copy the code
conclusion
Advantages:
-
Automatically subscribe
-
State can be obtained across components
Disadvantages:
-
Each time you force the inheritedWidget build to rebuild, virtually all of the subtrees’ builds are triggered, so a StatefulWidget needs to be wrapped to implement cache loading
-
There is no effective separation of view logic and business logic.
-
Unable to direct/directional notification. In fact, child widgets that rely on InheriteWidget call markNeedsBuild at the Element level before calling State’s didChangeDependencies, so they all rebuild
reference
- One-way data flow and two-way data flow