1

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

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!
  • 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 calls setState() 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 .
  • 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],
          ),
        ),
      );
      }
      }

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.
  • _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 call setState() 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,
          ),
        ),
      );
      }
      }

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 the setState(...) method to restart build itself.
    • 2. Use some packages dedicated to state management, such as Provider, Redux, and readers can view their detailed information on the pub.
  • 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);

fluter Utils tool library: https://github.com/yangchong211/YCFlutterUtils

Flutter mixed project code example: https://github.com/yangchong211/YCHybridFlutter


杨充
221 声望42 粉丝