Flutter | status management

Posted by aggrav8d on Sat, 12 Feb 2022 04:50:51 +0100

Sample code for this article

summary

There is an eternal theme "state management" in the responsive programming framework. Their problems and solutions are the same in React/Vue and fluent

Um, What does responsive programming mean????????

For responsive programming, the following answers refer to Baidu Encyclopedia: Responsive programming is a paradigm for data flow and change propagation In imperative programming, a+b = c means that the result of the expression is assigned to c, and then changing b or c will not affect A In reactive programming, the value of c is updated with the value of a or b

See here, I finally understand what response programming is

In fact, in the above example, a and b refer to the state, while c represents what the user can see, such as the interface.

In other words, when the state changes, the page will also be refreshed,

Personal understanding: responsive programming solves the problem of data consistency. Ensure that it can be synchronized to the page immediately after the state changes;

State management in fluent

In fluent, who should manage the state of StatefulWidget?

Widget itself? Is the parent widget managed by another object? The answer depends on the actual situation

The following are the most common ways to manage status:

  • Widget s manage their own state If the state is related to the appearance effect of the interface, such as color and animation, the state is best managed by the Widget itself
  • Widget manages the status of child widgets If the status is user data, such as the selected status and the position of the slider, the status is best managed by the parent Widget
  • Mixed management (both parent Widget and child Widget manage state) If a state is shared by different widgets, it is finally managed by their common parent Widget

In fact, the encapsulation of state management inside the Widget will be better, while the management in the parent Widget will be more flexible. Sometimes, if you are not sure how to manage it, the first choice should be to manage it in the parent Widget.

practice

  • Widget s manage their own state
class _TapBoxAState extends State<TapBoxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200,
        height: 200,
        child: Center(
          child: Text(_active ? "Active" : "Inactive",
              style: TextStyle(fontSize: 32, color: Colors.white)),
        ),
        decoration:
            BoxDecoration(color: _active ? Colors.lightBlue : Colors.green),
      ),
    );
  }
}
  • The parent Widget manages the state of the child Widget For the parent Widget, it is usually a better way to manage the status and tell the child Widget to update appropriately. For example, IconButton is an icon button. It is a stateless Widget. Our parent Widget needs to know whether the button is clicked to take corresponding treatment Examples
//------------------------ ParentWidget -----------------------
class ParentWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapBoxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: TapBoxB(
        active: _active,
        onChange: _handleTapBoxChanged,
      ),
    );
  }
}
//------------------------ TapBoxB -----------------------
class TapBoxB extends StatelessWidget {
  final bool active;
  final ValueChanged<bool> onChange;

  TapBoxB({Key key, this.active, this.onChange});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Container(
        width: 200,
        height: 200,
        child: Center(
          child: Text(active ? "Active" : "Inactive",
              style: TextStyle(fontSize: 32, color: Colors.white)),
        ),
        decoration:
            BoxDecoration(color: active ? Colors.lightBlue : Colors.green),
      ),
      onTap: () => onChange(!active),
    );
  }
}

In the above chestnut, TapBoxB transfers its state to the parent component through callback. The state is managed by the parent component, so its parent component is StatefullWidget. However, since TapBoxB itself does not manage any state, it is StatelessWidget The build method will be re executed every time setState is used to transfer the state to the sub components. Therefore, TabBoxB does not need to manage the state and can be used directly The execution effect is the same as the above figure

  • Mixed state management For some components, mixed management is very useful. The component itself manages some internal states, while the parent component manages some other external states Example:
//------------------------ ParentWidget -----------------------
class ParentWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapBoxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: TapBoxC(
        active: _active,
        onChange: _handleTapBoxChanged,
      ),
    );
  }
}

//------------------------ TapBoxC -----------------------
class TapBoxC extends StatefulWidget {
  final bool active;
  final ValueChanged<bool> onChange;

  TapBoxC({Key key, this.active, this.onChange});

  @override
  State<StatefulWidget> createState() => _TapBoxCState();
}

class _TapBoxCState extends State<TapBoxC> {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails detailis) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails detailis) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChange(!widget.active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      onTapDown: _handleTapDown,
      //press events 
      onTapUp: _handleTapUp,
      //Lift event
      onTapCancel: _handleTapCancel,
      //Cancel event
      child: Container(
        width: 200,
        height: 200,
        child: Center(
          child: Text(widget.active ? "Active" : "Inactive",
              style: TextStyle(fontSize: 32, color: Colors.white)),
        ),
        decoration: BoxDecoration(
            color: widget.active ? Colors.lightBlue : Colors.green,
            border: _highlight
                ? Border.all(color: Colors.teal[700], width: 10)
                : null),
      ),
    );
  }
}

The effect is as follows:

A frame will appear when the finger is pressed. When the finger is lifted, the frame will disappear. After clicking, the color of the component will change For developers, they only care about whether the component is in the active state, not the specific implementation of the border. Therefore, we hide the status of the border inside and only expose the active state outside

  • Global state management When the application needs some cross components and the routing state needs to be synchronized, the above methods are difficult to be competent. For example, when modifying the application language on the setting page, in order to make the setting take effect in real time, we expect that when the language state changes, the language dependent components in the App can be rebuilt, but these language dependent components are not together, so it is difficult to manage this situation by using the above methods This is the correct way to handle the communication between components far away through a global state manager. At present, there are two solutions: 1. Implement a global event bus, corresponding the state change of the language to an event, and then subscribe to the language change event in the initState method of the language dependent component in the App. When the user switches the language, the component subscribing to this event will receive the notification, and then reset the setState after receiving the notification 2. Use some packages dedicated to state management, such as Provider and Redux. For specific use, you can check the details on pub

reference resources

Actual combat of Flutter