Stupid Simple State Management for Flutter

Jonathan Aird
4 min readApr 18, 2020

Note: I turned this concept into a state management library called Upstate. However, I didn’t end up using the observable library as it hasn’t been maintained in years and instead built everything from scratch. Check it out!

State management in Flutter is pretty much the black sheep of the framework. While almost anything you would want to do with your UI, from animations to navigation to text styling, have extremely simple prebuilt widgets or at least the ability to very quickly come up with your own solution, there are no built in state management solutions in Flutter and the options that are available are generally complicated and difficult to learn.

What I’ve tried to do is to create a pattern that fits in with the rest of Flutter in that it is easy, simple, and elegant. All we need to work with this pattern are 2 basic ingredients:

— The observable Dart library

—and context.findAncestorWidgetOfExactType<T extends Widget>

Our strategy here is to contain the app state in a widget that wraps our entire app (in a similar way that MaterialApp and Scaffold provide information for their children) and access the state by searching up the widget tree. State information should be stored in observables so that when the state changes we can notify widgets that depend on those values to update their state.

Let’s started with with the basic counter example. The first thing we want to do is create our app state widget that wraps our whole app which initializes the state as an ObservableMap. We will also need a getter for the state. Then wrap our entire app in the AppState widget:

To access our state we need to use the initState method in our stateful widget state class in order to have access to the build context. We get the AppState widget using findAncestorWidgetOfExactType and get the state object from it and initialize the counter state as 0. In order to know when the state has changed (in the case where some other widget has changed the state) we listen for changes in the object itself using state.changes.listen and call setState whenever there has been a change.

We also have to save the StreamSubscription from changes.listen so that we can cancel the subscription in dispose when the widget is removed from the tree (make sure to import ‘dart:async’ for the StreamSubscription).

Doing things this way is especially nice because we can simply provide widgets direct access to the global app state and we can update the global app state directly without ever having to call setState() since it’s called automatically. Notice that in the _incrementCounter method we simply add 1 to the counter property of the global app state. Pretty elegant, no?

Now what I’ve shown is not the optimal configuration for a large scale app. For one, you should initialize global app state in the AppState widget rather than in a widget that depends on it to make it robust if you don’t happen to add that widget to the tree and so you can have a holistic view of your app state. And obviously in this example there is no real reason to have a global state and a stateful widget is just fine.

Another problem is that you don’t want every widget that depends on your app state to update every time any miscellaneous part of the state is updated.

For this you could divide your app state into sections that correspond to sections of your app and create an ObservableMap for each section. Thus, your state object could be a Map<String, ObservableMap> and you would change the code to get your state to the following:

_state = context.findAncestorWidgetOfExactType<AppState>().state['section'];

When you’re listening for changes in your ObservableMap you can also check the key of the key/value pair that changed and decide whether to call setState:

stateChanges = _state.changes.listen((event){
for(MapChangeRecord change in event){
if(change.key=='counter'){
setState(() {});
}
}
});

This pattern is very simple but you can adapt it to do a number of different things. For example, if a some part of your app needs state but doesn’t need global app state, you can wrap just that section of your app in a AppState widget or even name it something else like SectionState so that you could have both global app state and state for just that part of your app. That SectionState widget will only be accessible to that part of your app and can be removed from the tree if it’s not needed. It can also depend on a state widget higher up in the tree.

You can also use ObservableList instead of ObservableMap or create your own by extending the Observable class.

As far as I can tell there are no obvious performance issues that come with this pattern but I have not tested it thoroughly. findAncestorWidgetOfExactType is O(n) with the depth of the widget tree but it is only called once during initState of a widget that needs app state. When displaying a list of many widgets it should be preferable to lift the accessing of app state to a single stateful widget that would display the elements of the list rather than each accessing the app state.

There may be a way to abstract this pattern into a library but I haven’t thought of how to do that yet… Anyways have fun!

--

--