catalogue

  • 01. What is state management
  • 02. Classification of status management schemes
  • 03. Application scenarios of status management
  • 04. Widgets manage their own state
  • 05. Widgets Manage the status of child widgets
  • 06. Simple mixed management state
  • 07. How to manage global status
  • 08.Provider Usage method
  • 09. Subscribe to monitor the change status

recommended

  • Fluter Utils utility library: github.com/yangchong21…
  • The Flutter hybrid project code example: github.com/yangchong21…

01. What is state management

  • There is a constant theme in responsive programming frameworks — “State management”
    • In Flutter, think of a question,StatefulWidgetWho should manage the state of?
    • The Widget itself? The parent Widget? Will be? Or another object? The answer is it depends!
  • The following are the most common ways to manage state:
    • Widgets manage their own state.
    • Widgets manage child Widget state.
    • Mixed management (both parent and child widgets manage state).
    • State management of different modules.
  • How do you decide which management method to use? Here are some principles to help you make your decision:
    • 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.
    • If multiple modules need to share the same state, then how to deal with it? You can use Provider.
    • If a property is modified, multiple local data needs to be refreshed. For example, if the user city ID data is modified, the interface data at n of the home page will be refreshed. At this time, the status can be modified by subscription listening

02. Classification of status management schemes

  • SetState State management
    • Advantages:
      • Especially suitable for simple scenes, simple logic, easy to understand and implement
      • What you see is what you get
    • disadvantages
      • The coupling between logic and view is serious, and the maintainability is poor under complex logic
      • Data transmission is based on dependency transmission, which is difficult to maintain and poor readability when the hierarchy is deep
  • InheritedWidget state management
    • advantages
      • Convenient data transfer and can be decoupled from the logic and view based on the InheritedWidget
      • Flutter is embedded, basic and stable without code intrusion
    • disadvantages
      • A more basic class that is less friendly than a packaged third-party library
      • Extra attention is required for performance, as the refresh range can affect performance if it is too large
  • Provider Status Management
    • advantages
      • Features complete, covering all the functionality of the ScopedModel and InheritedWidget
      • The data logic is perfectly integrated into the Widget tree, and the code is neatly structured to manage local and global state
      • Multiple models and resource collection issues were solved
      • Optimized and differentiated the providers used in different scenarios
      • Supports asynchronous state management and Provider dependency injection
    • disadvantages
      • Improper use may cause performance problems (rebuild caused by large context)
      • Data synchronization before local state is not supported
  • Subscribe to listen for change status
    • There are two types: one is bus event notification (which is a subscription + observation), and the other is interface registration callback.
    • Interface callback: Due to the use of callback function principle, so the real-time data transfer is very high, equivalent to direct call, generally used in functional modules.
    • Bus events: Interactions between components greatly reduce the coupling between them, resulting in simpler code, lower coupling, and improved code quality.

03. Application scenarios of status management

  • SetState State management
    • Suitable for widgets to manage their own state. This is very common. Call setState to refresh your Widget’s changed state.
    • It is also common for widgets to manage the state of child widgets. But the correlation is strong.

04. Widgets manage their own state

  • _TapboxAState class:
    • Manage TapboxA status.
    • define_active: Determines the Boolean value of the current color of the box.
    • define_handleTap()Function that updates when the box is clicked_activeAnd callsetState()Update the UI.
    • Implements all of the interactive behavior of the widget.
  • The following code
    // TapboxA manages its own state.
    
    //------------------------- 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],),),); }}Copy the code

05. Widgets Manage the status of child widgets

  • So let’s see, what are these

    typedef ValueChanged<T> = void Function(T value);
    Copy the code
  • It is often a good idea for the parent Widget to manage state and tell its children when to update.

    • For example,IconButtonIs an icon button, but it is a stateless Widget because we think the parent Widget needs to know if the button is clicked to take appropriate action.
    • In the following example, TapboxB exports its state to its parent through callbacks, and the state is managed by the parent, so its parent isStatefulWidget.
  • ParentWidgetState class:

    • For TapboxB management_activeState.
    • implementation_handleTapboxChanged(), the method that is called when the box is clicked.
    • Called when the state changessetState()Update the UI.
  • TapboxB class:

    • inheritanceStatelessWidgetClass, because all state is handled by its parent component.
    • When a click is detected, it notifies the parent component.
    // ParentWidget manages state for 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 manages child Widget state"),
          ),
          body: new ListView(
            children: [
              new Text("Widget manages child Widget state"),
              newTapboxB( 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 newTabboxBState(); }}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],),),); }}Copy the code

06. Simple mixed management state

  • For some components, a hybrid management approach can be very useful.
    • In this case, the component itself manages some internal state, while the parent manages some other external state.
  • In the following TapboxC example
    • A dark green border appears around the box when the finger is pressed, and disappears when it is lifted. After clicking finish, the color of the box changes.
    • TapboxC it_activeState is exported to its parent component, but managed internally_highlightState.
    • This example has two state objects_ParentWidgetStateand_TapboxCState.
  • _ParentWidgetStateC Class:
    • management_activeState.
    • implementation_handleTapboxChanged()Called when the box is clicked.
    • When clicking on the box and_activeCalled when the state changessetState()Update the UI.
  • _TapboxCStateObject:
    • management_highlightState.
    • GestureDetectorListen for all TAP events. When the user clicks down, it adds highlighting (dark green border); Highlighting is removed when the user releases it.
    • Updates when pressed, lifted, or unclicked_highlightState, callsetState()Update the UI.
    • When clicked, the change in state 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("Simple hybrid management state"),
          ),
          body: new Container(
            child: new ListView(
              children: [
                new Text("_ParentWidgetCState Status Management"),
                new Padding(padding: EdgeInsets.all(10)),
                new Text(
                  _active ? 'Active' : 'Inactive',),new Padding(padding: EdgeInsets.all(10)),
                new Text("_TapboxCState State Management"),
                newTapboxC( 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) {
        // Add a green border when pressed and unhighlight when lifted
        return new GestureDetector(
          onTapDown: _handleTapDown, // Handle the press event
          onTapUp: _handleTapUp, // Handle the lift event
          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,),),); }}Copy the code

07. How to manage global status

  • When the application needs some state synchronization across components, including across routes, the above approach is not adequate.
    • Page, for example, we have a set, it can set the language of the application, we in order to let the Settings take effect in real time, we expect state changes in language, depend on the application of language in the APP component to be able to build, but these depend on the language application components and set the page is not together, so it is difficult to manage the situation with the above method.
    • In this case, the correct approach is to handle this communication between distant components through a global state manager.
  • At present, there are two main approaches:
    • 1. Implement a global event bus, correspond the language state change to an event, and then rely on the components of the application language in the APPinitStateMethod to subscribe to the event of a language change. When the user sets the page switch language, we publish the language change event, and the component that subscribed to this event is notified and called upon receiving the notificationsetState(...)Methods tobuildJust look at yourself.
    • 2. Use some special packages for state management, such as Provider, Redux, readers can view their details in pub.
  • Give a simple case to practice
    • In this example, we use the Provider package to share state across components, so we need to define the relevant Provider.
    • The login user information, APP subject information, and APP language information need to be shared. Since these information changes immediately notify other dependent widgets of updates to the information, we should use themChangeNotifierProviderIn addition, these changes are required to update the Profile information and persist it.
    • To sum up, we can define aProfileChangeNotifierBase class, and then let the Model you want to share inherit from that class,ProfileChangeNotifierThe definition is as follows:
      class ProfileChangeNotifier extends ChangeNotifier {
        Profile get _profile => Global.profile;
      
        @override
        void notifyListeners() {
          Global.saveProfile(); // Save the Profile changes
          super.notifyListeners(); // Notify dependent widgets of updates}}Copy the code
    • User state
      • The user status updates and notifies its dependencies when the login status changes, which we define as follows:
      class UserModel extends ProfileChangeNotifier {
        User get user => _profile.user;
      
        // Whether the APP is logged in (if there is user information, it proves that the APP is logged in)
        bool getisLogin => user ! =null;
      
        // When user information changes, update the user information and notify the descendant Widgets that depend on it to update it
        set user(User user) {
          if(user? .login ! = _profile.user? .login) { _profile.lastLogin = _profile.user? .login; _profile.user = user; notifyListeners(); }}}Copy the code

08.Provider Usage method

8.1 Correctly Initializing the Provider

  • As shown below, create is the parameter that must be passed
    ChangeNotifierProvider(
      create: (_) => MyModel(),
      child: ...
    )
    Copy the code
  • How to apply it in actual development
    builder: (BuildContext context, Widget child) {
        return MultiProvider(providers: [
          ChangeNotifierProvider(create: (context) => BusinessPattern()),
        ]);
    },
    Copy the code
  • And then what is Business Charter?
    class BusinessPattern extends ChangeNotifier { PatternState currentState = PatternState.none; void updateBusinessPatternState(PatternState state) { if (currentState.index ! = state.index) {logutils. d(' currentState :$currentState'); Logutils.d (' Update mode :$state'); currentState = state; notifyListeners(); }}}Copy the code

8.2 How to Obtain the Provider Value

  • One is provider.of (context), for example:
    Widget build(BuildContext context) {
      final text = Provider.of<String>(context);
      return Container(child: Text(text));
    }
    Copy the code
    • Problem: Since the Provider listens for Value changes and updates the entire context, the cost of refreshing the Widget returned by the build method can be very high if the Widget is too large or complex. So how can we further control the update scope of our widgets?
    • Solution: One solution is to wrap the Widget that really needs to be updated into a separate Widget and put the value method inside that 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); }}Copy the code
  • Consumer is another way to value a Provider
    • The Consumer can directly get the context and pass the Value as a parameter to the Builder, which is undoubtedly more convenient and intuitive to use and greatly reduces the cost of the developer to control the refresh range.
    Widget getWidget2(BuildContext context) { return Consumer<BusinessPattern>(builder: (the context, businessModel, child) {switch (businessModel. CurrentState) {case PatternState. None: return the Text (" mode "); break; Case PatternState.normal: Return Text(" Normal "); break; Case PatternState.small: Return Text(" Small screen mode "); break; Case PatternState. Overview: Return Text(" Full-screen mode "); break; Default: return Text(" other mode "); return SizedBox(); }}); }Copy the code
  • Selector is another way to value a Provider
    • Selector is a feature introduced in 3.1 to further control the update range of widgets by minimizing the listening refresh range
    • Selector: is a Function that passes in a Value, and asks us to return the specific property that’s being used in the Value.
    • ShouldRebuild: This Function passes in two values, one of which is the old value that was held before, and the new value that is returned this time by the selector. This is how we control whether we need to refresh the Widget in the Builder. If shouldRebuild is not implemented, the pre and next are deeply compared by default. If not, return true.
    • Builder: The place where the Widget is returned, and the argument defined by the second argument is the one we just returned in 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(" no pattern "); break; Case PatternState.normal: Return Text(" Normal "); break; Case PatternState.small: Return Text(" Small screen mode "); break; Case PatternState. Overview: Return Text(" Full-screen mode "); break; Default: return Text(" other mode "); return SizedBox(); }});Copy the code

8.3 Changing the Provider Status

  • How do I invoke change status management
    BusinessPatternService _patternService = serviceLocator<BusinessPatternService>(); / / modify state _patternService. NonePattern (); _patternService.normalPattern();Copy the code
  • Then take a look at the code that implements 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); }}Copy the code

8.4 Refreshing The Provider

  • When the state changes, widgets are rebuilt, not recreated (the reuse mechanism is related to keys; widgets are rebuilt if the key changes)

09. Subscribe to monitor the change status

  • First, define the abstract class. You also need to write the concrete implementation class
    typedef LocationDataChangedFunction = void Function(double); The abstract class LocationListener {/ / / callback registration data change void registerDataChangedFunction (LocationDataChangedFunction function); / / change/remove data correction void unregisterDataChangedFunction (LocationDataChangedFunction function); / / / update the data changes in the 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); }}Copy the code
  • So how do you use it? Add an interface callback listener on the page you want to use
    _locationListener.registerDataChangedFunction(_onDataChange); Void _onDataChange(double p1) {Copy the code
  • So how do you send events at this time
    LocationListener _locationListener = locationService(); _locationListener. LocationDataChangedCallback (520.0);Copy the code

Fluter Utils utility class library:Github.com/yangchong21…

Code examples of the Flutter hybrid project:Github.com/yangchong21…