Let me help you to understand and choose a state management solution for your app

Jorge Coca
Flutter Community
Published in
6 min readSep 17, 2018

State management in Flutter is a hot topic. The options available are numerous, and while that might be awesome, it is easy to feel overwhelmed, easily confused and lost when trying to pick the best solution that fits your needs. I have been there too. However, I have found a solution that fits my needs, so let me share it with you.

Image obtained from http://danview.net/sharing-caring/

In order to pick a solution that fits your needs, the first thing to do actually is to identify those needs, and set up some goals and expectations. For my purposes, I identified these:

  • Allow steady development velocity without sacrificing code quality
  • Separate presentation logic from business logic
  • Easy to understand; hard to break.
  • Predictable and widely adopted

Given these constraints, these are the options that we will be exploring:

  • Using setState() and StatefulWidgets
  • ScopedModel
  • BLoC (Business Logic Component)
  • Redux

Understanding the difference between local and global state

Before we deep dive into the the different options under analysis, one thing that might help us to understand better how to make this choice is understanding what’s part of local state, and what’s part of the global state.

In order to do that, let’s consider a practical example: imagine a simple login form, where the user is allowed to enter a username and password, and then get the “identity” object from a backend if the credentials match. In this example, any type of validation of the login form could be considered local state, since the scope of those rules only applied to this component, and the rest of our application does not need to know about it. However, the “identity” object obtained from the backend can be considered part of our global state, since it changes the entire scope of the app (non-authenticated vs. authenticated), and probably other components are dependent on it.

TL;DR: Show me your results

If you do not want to wait to see the results, or you are not interested in the exploration itself, here you can find here a quick snapshot of my findings:

Quick overview of my state management exploration

My recommendation is to use the BLoC pattern for local state management, and rely on Redux for supporting your global state scope. Specially, if these are complex apps that will grow overtime.

Why not to use setState() ?

Using setState() inside your widgets is great for quick prototyping, and you will get immediate feedback. However, it does not help to achieve our goals: presentation and business logic are all part of the same class, breaking the principles of clean code and quality. Code maintenance might become a challenge in the future as your application grows, and therefore, I do not recommend using setState() other than for quick prototyping.

ScopedModel, a step in the right direction

ScopedModel is a third party package maintained by Brian Egan. It lets us create Model objects, and use a method called notifyListeners() whenever we think it is necessary; for example, on any property change in our model.

class CounterModel extends Model {  int _counter = 0;  int get counter = _counter;  void increment() {
_counter++;
notifyListeners();
}
}

In our widgets, we can use ScopedModelDescendant to react to the changes in the model:

class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModel<CounterModel>(
model: new CounterModel(),
child: new Column(children: [
new ScopedModelDescendant<CounterModel>(
builder: (context, child, model) => new Text('${model.counter}'),
),
new Text("Another widget that doesn't depend on the CounterModel")
])
);
}
}

As opposed to setState() , if we use ScopedModel we can separate our business logic from our presentation logic, which is good. However, it presents a few limitations:

  • If your Model gets complex, it might be challenging to know when to properly call notifyListeners() to avoid unnecessary updates.
  • The API exposed by Model does not describe accurately the asynchronous nature of UI apps in general.

With all this information, I do not recommend the use of ScopedModel unless your state is very easy to manage; if your application is complex enough, I do not believe this the right answer to support that complexity and growth.

BLoC, a powerful solution

BLoC is a pattern created and used by Google; it will help us to achieve a few things:

  • Separate business logic from presentation logic
  • EMbrace the asynchronous nature of UI apps
  • Can be reused across different Dart applications, regardless of being a Flutter app or an Angular Dart application.

The idea behind BLoC is very simple:

  • BLoC exposes Sink<I> APIs to describe asynchronous inputs to our component
  • BLoC exposes Stream<T> APIs to describe asynchronous outputs from our component
  • Finally, we can use a StreamBuilder widget to manage the stream of data, removing from us the effort of maintaining a subscription to the stream and redraws of the widget tree.

Since this is heavily used and recommended by Google, they have really good examples, like this one: https://github.com/filiph/state_experiments/tree/master/shared/lib/src/bloc_complex

I totally recommend using BLoC in your application, specially to manage local state. Even for global state management, I believe it can be a great solution; however, you might be facing some challenges in this area, such as how and where to inject a BLoC properly that is accessed by different UI components, and that’s where I believe a solution like Redux shines.

Redux and BLoC, the perfect combo for me

One of the goals that I described at the beginning of the article was that I am looking for something that is widely adopted and predictable. Well, that’s Redux.

Redux is a pattern that, combined with some tooling, will help us to manage our global state. It is built with three core principles in mind:

  • Single source of truth: the state of your whole application is store in an object tree within a single store .
  • State is read-only: the only way to change the state is to emit an action, an object describing what happened.
  • Changes are made with pure functions: to specify how the state is transformed by actions, you write pure reducers .
Image obtained from this awesome post by Novoda: https://blog.novoda.com/introduction-to-redux-in-flutter/

Redux is a pattern widely adopted by web developers, so having a common ground also on mobile will help us to benefit from each other.

Brian Egan maintains both redux and flutter_redux, and has created also a fantastic ToDo application with many different architecture patterns, including Redux.

Given all the qualities of Redux, I totally recommend its use to manage global state, but make sure no local state changes are introduced in your Redux architecture if you want to scale and grow your application.

Final thoughts

There’s not right or wrong solution. In order to pick a tool, or adopt a pattern, it is important to understand your necessities. For me and my needs, the combo BLoC and Redux is going to help me to grow my projects fast and in a safe manner, while also facilitating onboarding other developers, since the tools available are well understood by the community. However, not everyone has the same needs, or even down the line we find issues or a better tool; it is important then to always be curious, learn and think if that’s the right solution for you.

You can follow me on https://twitter.com/jcocaramos and see more code here on my public Github https://github.com/jorgecoca

--

--

Jorge Coca
Flutter Community

Android engineer at @bmwna. Born in Madrid, living in Chicago. I have watched La La Land more times than you… and I love singing and dancing in public xD