Catalog Introduction
- 01. What is state management
- 02. Classification of state management solutions
- 03. State management usage scenarios
- 04.Widget manages its own state
- 05. Widget manages the status of sub Widgets
- 06. Simple mixed management status
- 07. How to manage the global state
- 08. Provider usage method
- 09. Subscribe to monitor modification status
recommend
- fluter Utils tool library: https://github.com/yangchong211/YCFlutterUtils
- Flutter mixed project code example: https://github.com/yangchong211/YCHybridFlutter
01. What is state management
Responsive programming frameworks will have an eternal theme-"State Management"
- In Flutter, think of a question, who should manage the status of
StatefulWidget
- Widget itself? The parent Widget? Metropolis? Or another object? The answer is that it depends on the actual situation!
- In Flutter, think of a question, who should manage the status of
The following are the most common ways to manage status:
- Widget manages its own state.
- Widget manages the state of sub-Widgets.
- Hybrid management (both parent Widget and child Widget manage state).
- State management of different modules.
How to decide which management method to use? Here are some principles to help you make a decision:
- If the state is user data, such as the selected state of a check box and the position of a slider, the state is best managed by the parent Widget.
- If the state is related to the appearance of the interface, such as color and animation, the state is best managed by the Widget itself.
- If a certain state is shared by different Widgets, it is better to be managed by their common parent Widget.
- If multiple modules need to share a state, then how to deal with it, you can use Provider.
- If you modify a certain attribute, you need to refresh the data in multiple places. For example, if you modify the user's city id data, then refresh the interface data at the homepage n, this time you can use the subscription to monitor the modification status
02. Classification of state management solutions
setState state management
advantage:
- Especially suitable for simple scenarios, simple logic, easy to understand and easy to implement
- WYSIWYG, high efficiency
shortcoming
- Logic and view are seriously coupled, and maintainability is poor under complex logic
- Data transmission is based on dependency transmission, which is difficult to maintain and poor readability in the case of deeper levels
InheritedWidget state management
advantage
- Convenient data transmission, can achieve the effect of decoupling logic and view based on InheritedWidget
- Flutter built-in class, basic and stable, no code intrusion
shortcoming
- Belongs to a relatively basic class, not as friendly as a packaged third-party library
- Additional attention is required for performance, if the refresh range is too large, performance will be affected
Provider state management
advantage
- Complete functions, covering all functions of ScopedModel and InheritedWidget
- The data logic is perfectly integrated into the widget tree, the code structure is clear, and the local state and the global state can be managed
- Solve the problem of multiple models and resource recycling
- Optimized and differentiated providers used in different scenarios
- Support asynchronous state management and provider dependency injection
shortcoming
- Improper use may cause performance problems (rebuild caused by large context)
- Data synchronization before partial state is not supported
Subscribe to monitor modification status
- There are two types: one is bus event notification (a subscription + observation), and the other is interface registration callback.
- Interface callback: Due to the use of the callback function principle, the real-time performance of data transmission is very high, which is equivalent to direct calling, and is generally used on functional modules.
- Bus event: The interaction between components greatly reduces the coupling between them, making the code more concise, lower coupling, and improving the quality of our code.
03. State management usage scenarios
setState state management
- It is suitable for Widget to manage its own state. This is very common, calling setState to refresh its own widget to change the state.
- It is suitable for Widget to manage the state of sub-Widget, which is also relatively common. However, this correlation is relatively strong.
04.Widget manages its own state
_TapboxAState class:
- Manage the status of TapboxA.
- Definition
_active
: Boolean value that determines the current color of the box. - Define the
_handleTap()
function, which updates_active
when the box is clicked, and callssetState()
update the UI. - Realize all interactive behaviors of widgets.
code show as below
// TapboxA 管理自身状态. //------------------------- TapboxA ---------------------------------- class TapboxA extends StatefulWidget { TapboxA({Key key}) : super(key: key); @override _TapboxAState createState() => new _TapboxAState(); } class _TapboxAState extends State<TapboxA> { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return new GestureDetector( onTap: _handleTap, child: new Container( child: new Center( child: new Text( _active ? 'Active' : 'Inactive', style: new TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: new BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
05. Widget manages the status of sub Widgets
Let’s take a look at what these are below
typedef ValueChanged<T> = void Function(T value);
For the parent Widget, it is usually a better way to manage the state and tell its child Widget when to update.
- For example,
IconButton
is an icon button, but it is a stateless Widget, because we believe that the parent Widget needs to know whether the button is clicked to take corresponding processing. - In the following example, TapboxB exports its state to its parent component through a callback, and the state is managed by the parent component, so its parent component is
StatefulWidget
.
- For example,
ParentWidgetState
class:_active
status for TapboxB.- Implement
_handleTapboxChanged()
, the method to be called when the box is clicked. - When the status changes, call
setState()
update the UI.
TapboxB class:
- Inherit the
StatelessWidget
class, because all states are handled by its parent component. When a click is detected, it will notify the parent component.
// ParentWidget 为 TapboxB 管理状态. class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => new _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Widget管理子Widget状态"), ), body: new ListView( children: [ new Text("Widget管理子Widget状态"), new TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ], ), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatefulWidget{ final bool active; final ValueChanged<bool> onChanged; TapboxB({Key key , this.active : false ,@required this.onChanged }); @override State<StatefulWidget> createState() { return new TabboxBState(); } } class TabboxBState extends State<TapboxB>{ void _handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { return new GestureDetector( onTap: _handleTap, child: new Container( child: new Center( child: new Text( widget.active ? 'Active' : 'Inactive', ), ), width: 100, height: 100, decoration: new BoxDecoration( color: widget.active ? Colors.lightGreen[700] : Colors.grey[850], ), ), ); } }
- Inherit the
06. Simple mixed management status
For some components, a mixed management approach can be very useful.
- In this case, the component itself manages some internal state, while the parent component manages some other external state.
In the TapboxC example below
- When you press your finger, a dark green border will appear around the box, and when you lift it up, the border disappears. After clicking Finish, the color of the box changes.
- TapboxC exports its
_active
state to its parent component, but manages its_highlight
state internally. - This example has two status objects
_ParentWidgetState
and_TapboxCState
.
_ParentWidgetStateC
class:- Manage
_active
status. - Implement
_handleTapboxChanged()
, which is called when the box is clicked. setState()
update the UI when the box is_active
status of 06125c04dd1794 changes.
- Manage
_TapboxCState
object:- Manage
_highlight
state. GestureDetector
listens to all tap events. When the user clicks, it adds a highlight (dark green border); when the user releases it, it removes the highlight._highlight
state when pressing, lifting, or canceling the click, and callsetState()
update the UI.When clicked, the state change is passed to the parent component.
//---------------------------- ParentWidget ---------------------------- class ParentWidgetC extends StatefulWidget { @override _ParentWidgetCState createState() => new _ParentWidgetCState(); } class _ParentWidgetCState extends State<ParentWidgetC> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("简单混合管理状态"), ), body: new Container( child: new ListView( children: [ new Text("_ParentWidgetCState状态管理"), new Padding(padding: EdgeInsets.all(10)), new Text( _active ? 'Active' : 'Inactive', ), new Padding(padding: EdgeInsets.all(10)), new Text("_TapboxCState状态管理"), new TapboxC( active: _active, onChanged: _handleTapboxChanged, ) ], ), ), ); } } //----------------------------- TapboxC ------------------------------ class TapboxC extends StatefulWidget { TapboxC({Key key, this.active: false, @required this.onChanged}) : super(key: key); final bool active; final ValueChanged<bool> onChanged; @override _TapboxCState createState() => new _TapboxCState(); } class _TapboxCState extends State<TapboxC> { bool _highlight = false; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false; }); } void _handleTapCancel() { setState(() { _highlight = false; }); } void _handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { // 在按下时添加绿色边框,当抬起时,取消高亮 return new GestureDetector( onTapDown: _handleTapDown, // 处理按下事件 onTapUp: _handleTapUp, // 处理抬起事件 onTap: _handleTap, onTapCancel: _handleTapCancel, child: new Container( child: new Center( child: new Text(widget.active ? 'Active' : 'Inactive', style: new TextStyle(fontSize: 32.0, color: Colors.white)), ), width: 200.0, height: 200.0, decoration: new BoxDecoration( color: widget.active ? Colors.lightGreen[700] : Colors.grey[600], border: _highlight ? new Border.all( color: Colors.teal[700], width: 10.0, ) : null, ), ), ); } }
- Manage
07. How to manage the global state
When the application requires some cross-component (including cross-routing) state needs to be synchronized, the method described above is difficult to do.
- For example, we have a settings page where you can set the language of the application. In order to make the settings take effect in real time, we expect that when the language status changes, the components that depend on the application language in the APP can be rebuilt, but these components that depend on the application language It is not together with the settings page, so this situation is difficult to manage with the above method.
- At this time, the correct approach is to use a global state manager to handle the communication between components that are far apart.
There are currently two main methods:
- 1. Implement a global event bus, correspond the language state change to an event, and then subscribe to the language change event in
initState
When the user switches the language on the settings page, we publish a language change event, and the component that has subscribed to this event will receive a notification. After receiving the notification, call thesetState(...)
method to restartbuild
itself. - 2. Use some packages dedicated to state management, such as Provider, Redux, and readers can view their detailed information on the pub.
- 1. Implement a global event bus, correspond the language state change to an event, and then subscribe to the language change event in
Give a short answer case to practice
- In this example, the Provider package is used to realize cross-component state sharing, so we need to define related Providers.
- The states that need to be shared include login user information, APP theme information, and APP language information. Since this information is changed immediately to notify other Widgets that rely on the information to update, we should use
ChangeNotifierProvider
. In addition, after this information is changed, the Profile information needs to be updated and persisted. In summary, we can define a
ProfileChangeNotifier
base class, and then let the Model that needs to be shared inherit from this class.ProfileChangeNotifier
defined as follows:class ProfileChangeNotifier extends ChangeNotifier { Profile get _profile => Global.profile; @override void notifyListeners() { Global.saveProfile(); //保存Profile变更 super.notifyListeners(); //通知依赖的Widget更新 } }
user status
The user status is updated when the login status changes, and its dependencies are notified. We define it as follows:
class UserModel extends ProfileChangeNotifier { User get user => _profile.user; // APP是否登录(如果有用户信息,则证明登录过) bool get isLogin => user != null; //用户信息发生变化,更新用户信息并通知依赖它的子孙Widgets更新 set user(User user) { if (user?.login != _profile.user?.login) { _profile.lastLogin = _profile.user?.login; _profile.user = user; notifyListeners(); } } }
08. Provider usage method
8.1 Initialize Provider correctly
As shown below, create is a parameter that must be passed
ChangeNotifierProvider( create: (_) => MyModel(), child: ... )
How to apply in actual development
builder: (BuildContext context, Widget child) { return MultiProvider(providers: [ ChangeNotifierProvider(create: (context) => BusinessPattern()), ]); },
Then take a look at what BusinessPattern is?
class BusinessPattern extends ChangeNotifier { PatternState currentState = PatternState.none; void updateBusinessPatternState(PatternState state) { if (currentState.index != state.index) { LogUtils.d('当前模式:$currentState'); LogUtils.d('更新模式:$state'); currentState = state; notifyListeners(); } } }
8.2 How to get Provider value
One is Provider.of(context) such as:
Widget build(BuildContext context) { final text = Provider.of<String>(context); return Container(child: Text(text)); }
- Problems encountered: Since the Provider will monitor the changes in Value and update the entire context, if the Widget returned by the build method is too large and complicated, the cost of refreshing is very high. So how can we further control the update scope of the Widget?
Solution: One solution is to encapsulate the Widget that really needs to be updated into an independent Widget, and put the value method inside the Widget.
Widget build(BuildContext context) { return Container(child: MyText()); } class MyText extends StatelessWidget { @override Widget build(BuildContext context) { final text = Provider.of<String>(context); return Text(text); } }
Consumer is another way to value Provider
Consumer can directly get the context and pass the Value together as a parameter to the builder. It is undoubtedly more convenient and intuitive to use, which greatly reduces the developer's work cost for controlling the refresh range.
Widget getWidget2(BuildContext context) { return Consumer<BusinessPattern>(builder: (context, businessModel, child) { switch (businessModel.currentState) { case PatternState.none: return Text("无模式"); break; case PatternState.normal: return Text("正常模式"); break; case PatternState.small: return Text("小屏模式"); break; case PatternState.overview: return Text("全屏模式"); break; default: return Text("其他模式"); return SizedBox(); } }); }
Selector is another way to value Provider
- Selector is a function introduced in 3.1. The purpose is to further control the update range of Widget and control the monitoring refresh range to a minimum
- selector: It is a Function, passing in Value and asking us to return the specific properties used in Value.
- shouldRebuild: This Function will pass in two values, one of which is the old value held before and the new value returned by the selector this time. We use this parameter to control whether we need to refresh the Widget in the builder. If shouldRebuild is not implemented, deep comparisons between pre and next will be performed by default (deeply compares). If they are not the same, return true.
builder: Where the Widget is returned, the parameter defined by the second parameter is the parameter we just returned in the selector.
Widget getWidget4(BuildContext context) { return Selector<BusinessPattern, PatternState>( selector: (context, businessPattern) => businessPattern.currentState, builder: (context, state, child) { switch (state) { case PatternState.none: return Text("无模式"); break; case PatternState.normal: return Text("正常模式"); break; case PatternState.small: return Text("小屏模式"); break; case PatternState.overview: return Text("全屏模式"); break; default: return Text("其他模式"); return SizedBox(); } } );
8.3 Modify Provider Status
How to call the modification status management
BusinessPatternService _patternService = serviceLocator<BusinessPatternService>(); //修改状态 _patternService.nonePattern(); _patternService.normalPattern();
Then look at the specific implementation code of normalPattern
class BusinessPatternServiceImpl extends BusinessPatternService { final BuildContext context; BusinessPatternServiceImpl(this.context); PatternState get currentPatternState => _getBusinessPatternState(context).currentState; BusinessPattern _getBusinessPatternState(BuildContext context) { return Provider.of<BusinessPattern>(context, listen: false); } @override void nonePattern() { BusinessPattern _patternState = _getBusinessPatternState(context); _patternState.updateBusinessPatternState(PatternState.none); } @override void normalPattern() { BusinessPattern _patternState = _getBusinessPatternState(context); _patternState.updateBusinessPatternState(PatternState.normal); } }
8.4 About Provider refresh
- After the status changes, the widget will only be rebuilt, but not recreated (the reuse mechanism is related to the key, and the widget will be regenerated if the key changes)
09. Subscribe to monitor modification status
First define the abstract class. You also need to write a specific implementation class
typedef LocationDataChangedFunction = void Function(double); abstract class LocationListener { /// 注册数据变化的回调 void registerDataChangedFunction(LocationDataChangedFunction function); /// 移除数据变化的回调 void unregisterDataChangedFunction(LocationDataChangedFunction function); /// 更新数据的变化 void locationDataChangedCallback(double angle); } class LocationServiceCenterImpl extends LocationListener { List<LocationDataChangedFunction> _locationDataChangedFunction = List(); @override void locationDataChangedCallback(double angle) { _locationDataChangedFunction.forEach((function) { function.call(angle); }); } @override void registerDataChangedFunction(LocationDataChangedFunction function) { _locationDataChangedFunction.add(function); } @override void unregisterDataChangedFunction(LocationDataChangedFunction function) { _locationDataChangedFunction.remove(function); } }
So how to use it? Add interface callback monitoring on the page that needs to be used
_locationListener.registerDataChangedFunction(_onDataChange); void _onDataChange(double p1) { //监听回调处理 }
So how to send the event, this time
LocationListener _locationListener = locationService(); _locationListener.locationDataChangedCallback(520.0);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。