Flutter State Management

user-image

Mar 26, 2019 - 8 minutes read

Level: Beginner N|Solid

To people who is just begin to learn Flutter, they often start with state management. However, it’s is not easy to understand and choose what kinds of state mangement to use. This artical will cover some content that beginner need to know:

  • How important is state?
  • Introduce some kinds of state management
  • Whole widget update: an example, Pros and Cons
  • Partly widget update: an example, Pros and Cons
  • Conclusion

How important is state?

When people say about the state of a widget, they refer about the current presentation of data’s value. In another word, it’s one of the data’s possible status. For example, in the default Flutter application created by using Android Studio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(_counter.toString()),
          RaisedButton(
            child: Text('increase'),
            onPressed: () {},
          )
        ],
      ),
    );
  }
}

Please notice that the variable _counter, its purpose is to hold the current value of the data. Then, let’s try to update the value of the _counter when user click to the button by changing the onPressed:

1
2
3
 onPressed: () {
     ++_counter;
 },

You have increase the _counter value by one when the button is click. However, the UI is not change?! Why? It’s because the _counter and the status of the application is not attached to each other. In other word, they are different. So how can we show the new value to user. Well, we have to trigger update function to rerender the UI area.

Introduce some kinds of state management

If you are an pro in Flutter, you may read lots of docs and know many of kinds of state management. However, you are not. So I will introduce some popular state managerment ways, and they can be divided in two categories, depending on the range it update the widget:

Whole widget update
  • setState : a function inside the widget to retrigger the build function
  • Widget parameter: changable values to pass to widget as parameters, a way to update the widget from outside
  • InheritedWidget and InheritedModel: 2 ways to update the whole widget by listen to the higher widget bypass from the context of the app tree.
  • redux : regularly, a redux store is often wraps the whole widget, so it belongs to whole widget update too.
Partly widget update
  • StreamBuilder and FutureBuilder : 2 ways to update partly the widget, whenever the value changes. Those two are often used in BLOC pattern
  • scoped model : update the widget whenever the value of the model changed
  • Whole widget update: example and when to use

    An example

    Let’s update old code

1
2
3
4
5
 onPressed: () {
     this.setState((){
         ++_counter;
     });
 },

The code above perform 2 actions:

  • First, it increase the value of _counter
  • Then, it retrigger build function so the new data is appear
Pros and Cons

Pros:

  • simpler than partly widget update methods
  • suitable with widgets that have less frequently changed data

Cons:

  • watsedly rebuild some area of widget where the data is not changed

Partly widget update: example and when to use

An example

Firstly, we have to create a BLOC that has a StreamObject to listen whenever the data change and increase method to update the data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:rxdart/rxdart.dart';

class CounterBloc {
  int initialCount = 0;
  BehaviorSubject<int> _subjectCounter;

  CounterBloc({this.initialCount}) {
    _subjectCounter = new BehaviorSubject<int>.seeded(this.initialCount);
  }

  Observable<int> get counterObservable => _subjectCounter.stream;

  void increment() {
    initialCount++;
    _subjectCounter.sink.add(initialCount);
  }

  void dispose() {
    _subjectCounter.close();
  }
}

After that, we rewrite the widget to utilize the BLOC above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class _MyHomePageState extends State<MyHomePage> {
  CounterBloc _counterBloc;
  @override
  void initState() {
    super.initState();
    _counterBloc = CounterBloc(initialCount: 0);
  }

  @override
  void dispose() {
    _counterBloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          StreamBuilder(
              stream: _counterBloc.counterObservable,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                return Text(snapshot.data.toString());
              }),
          RaisedButton(
            child: Text('increase'),
            onPressed: () {
              _counterBloc.increment();
            },
          )
        ],
      ),
    );
  }
}

Pros and Cons

Pros:

  • only update the UI area that the data changed
  • suitable with widgets that have more frequently changed data

Cons:

  • more verbose than whole widget update

Conclusion

There is no silver bullet between whole widget update methods and partly widget update methods. Developers have to give desision on what method to use depend on the app performance and code maintainability.