Flutter Tutorial: Pros and Cons of popular State Management Approaches

Nov 11, 2019

Flutter 105 by Scott Stoll

“What State Management solution should I use?” is perhaps the most often asked question in Flutter. To understand it best, perhaps we should look at another, similar question:

“What toppings should I get on my pizza?”

Seriously. How am I supposed to know what you like on your pizza, and how am I supposed to know which State Management approach is going to be best for your app AND for your personal ability to get that app out the door?

And therein lies the problem, grasshopper. There are plenty of good State Management solutions out there but not all of them are going to be a good fit for you, not just your app. If you’re really good with ScopedModel then it makes no sense for you to lose a lot of time trying to learn BLoC just because some fool (like this one) on the internet wrote an article (also like this one) saying that BLoC is the greatest thing since last week’s greatest thing. If you can create a solid, bug-free solution with ScopedModel using an MVSP architecture and get the app out the door 40 hours faster then this needs to be your thinking:

  • Are you charging a flat rate? Then you want to get a good quality app out the door as fast as possible… so use whatever you already know and are good with so you can get it done fast.
  • Are you charging by the hour and the customer has a large budget? Then maybe weigh things out a bit. Maybe it could be worth checking out a couple of solutions to see what fits the app best and will be easiest to maintain for several years, even if it runs the bill up 40-60 hours.
  • Are you charging by the hour and the customer is a pain in the neck, or you need overtime at work? Well, I would never advise anyone to take a week or two just to research things and teach yourself new techniques, running up the bill while you get someone else to pay for the time you spend adding new skills to your toolbox. But no one ever listens to me anyway, so you make up your own mind and don’t blame me if people get mad at you.

That said, each State Management solution has its own characteristics and each person has different preferences. But we’ve even got something more basic we need to look at first.

What exactly do people mean when they’re talking about State Management in Flutter? It turns out the answer isn’t nearly as straightforward as you might think.

What is State Management?

When we talk about State Management in Flutter, we’re actually talking about a combination of Architecture and something that is sort-of, kind-of like Dependency Injection, but not really… except maybe on Wednesdays during #HumpDayQandA.

Yeah, it’s like that. All of this will become more clear as you read on or, if you stop reading this article before the end, then you’ll probably walk away even more confused than when you started.

A word about code examples: I had hoped to make a lot more examples for this article but it quickly started to get out of hand since most of the techniques required three or more separate sets of example code with explanations of how they interact. In the future, we may make one article for each technique with a breakdown but that’s not what we’re doing today.

Let’s take a look at a few ways of handling State and see if any of them appeal to you than others. This is not an exhaustive list, not even close. These are just some of the more commonly used approaches:

setState

setState is the State Management approach used in the default Flutter counter app. When I first started using it, I was confused about exactly what it was doing but then I had a realization:

“What if they had named it setStateWith or setStateAfter?”

Suddenly it’s easy to understand what’s happening here.

What this does is call the build() method for the State it’s in but only after it does whatever you tell it to do inside the { }. So, you can think of it as setStateWith or setStateAfter.

Pros of setState

  • Very easy and straightforward to understand.
  • A great way to handle ephemeral State.

Per the docs: Ephemeral State is sometimes called UI State or local State. It’s the State you can neatly contain in a single widget.

Cons of setState

  • Not a good way to handle things involving app State (Global State and parts of the State you want to persist between sessions).
  • Using setState all over an app can become a maintenance nightmare very quickly because your State is scattered all over the place
  • Usually used within the same class as the UI code, mixing UI and business logic, which breaks clean code principles. In a tiny app, this is no big deal but it becomes a concern quickly when you have more than just a couple of screens. You can separate the logic with a little effort, but there are better ways to handle State Management once an app grows beyond a few pages.

InheritedWidget… A.K.A. “Flutter’s Monad”

“If you understand, no explanation is necessary. If you don’t understand, no explanation can help.”

InheritedWidget is one of those strange things in the Universe that some people understand instantly and others struggle with for years. Everyone can understand the basic idea, the block diagram is easy. The problem is that a lot of people can’t seem to figure out the details of how it does what it does, or how to write the code in a way that will make it work.

Guess which one I am?

The good news is that both ScopedModel and Provider are what I like to call, “InheritedWidget for humans”. They’re both a lot easier to understand and they do all the InheritedWidget heavy lifting under the hood, so you don’t have to worry about it.

My advice is that people shouldn’t take a lot of time trying to understand InheritedWidget. Almost no one uses it directly anymore and you get the same benefits from ScopedModel and Provider, anyway.

What InheritedWidget, ScopedModel, and Provider are all doing

In short, without a better way to do things, a Flutter UI can would pass data all the way down the tree from parent to child, down to the point where you use it. To say this is wasteful can be an understatement, especially once your app gets to be any respectable size.

These three approaches quite literally “short circuit” passing data down the tree. In electronics, a short circuit is a path of electricity that is “shorter” than what was happening before… often resulting in a machine losing its “magic smoke” before all the lights go dark. Here, what happens is these three make a path for your State data that is much shorter than passing it down the tree.

Four things have to be done to make this happen:

  1. The object that changes has to have a way to signal that it’s been changed.
  2. There needs to be an access point that exposes your data, “injecting” it into your UI tree, so it’s available anywhere in the tree below that point.
  3. We need to make a new Context, so your exposed data is included in the new Context. This will allow it to be accessed by anything using that Context.
  4. We need to have something that fetches the data, so we can use it.

Think of it this way. Instead of passing your data all the way down the tree from the top, we’re passing the data into the middle of the tree, just above where we’re going to use it. It’s a good practice to make the “access point” (your Provider or ScopedModel) as low in the tree as you can, so you don’t “pollute the scope” by injecting it higher than needed.

And that’s why we say it’s sort-of like Dependency Injection.

ScopedModel and Provider

These two might seem redundant because the big difference is that Provider gives you a lot more options, and it can use ChangeNotifier which is part of the Flutter framework. ScopedModel is a lot more simplified and you extend Model, which is a specific class within the ScopedModel package.

Think of it this way: ScopedModel is like a version of Provider for Dummies. Provider is a lot more flexible, but not quite as easy to use.

Pros of ScopedModel

  • UI logic and business logic are clearly separated
  • Can be set up with unidirectional data flow without much difficulty, gaining the main benefit of Redux

Cons of ScopedModel

  • Too easy to accidentally call unnecessary updates. Not every change to the State of an object needs to trigger an update all the time, but when using ScopedModel or Provider, they will.

>> Read more about Flutter Provider for humans by Scott Stoll

Flutter BLoC

BLoC stands for Business Logic Components, and it’s much more of an architecture than the others we’ve discussed so far; some have even likened it to MVVM (Model, View, View Model). Unlike the others, BLoC makes heavy use of Streams and it’s often used in conjunction with Provider, which is often used as a way of exposing the BLoC for the UI.

BLoC implements the Observer pattern, with it your events are fed into a Stream that is the input into a logic block. The logic block figures out what response it needs to give and then sends that response back out. But a key factor here is the block doesn’t go running around doing things as a result of the input you give it. It simply processes the input and outputs a result. That result is then sent out to some other part of your app and it’s that part that does something with the output.

But what does it do? Anything you want. Maybe you fed it a String that said “Smith” and the logic in the BLoC was made to return a list of everyone in your contacts list with that last name. Maybe the BLoC gets fed the number of clock ticks that have passed since an animation started and the BLoC’s job is to calculate the position of your bouncing ball based on how long ago you pressed the button. You can make it do whatever you want, it’s an architecture, a method of handling State… what you do with it is entirely up to you.

Note that there are two types of streams. A single subscription stream allows for only one listener and you can’t reassign it to another listener, ever. The broadcast stream can have any number of listeners, all at the same time.

Pros of BLoC

  • Easy to separate UI from logic
  • Easy to test code
  • Easy to reuse code
  • Good performance

Cons of BloC

  • Technically, you need to use streams in both directions, creating a lot of boilerplate. However, many people cheat this by using streams only from the backend to the UI, but when events occur they’re simply calling functions directly instead of feeding those events into a sink.
  • More boilerplate than ScopedModel or Provider, but it can be worth it for anything larger than a small app.

Redux

When early adopters started working with Flutter in 2017, we had three choices for State Management. We had setState, InheritedWidget and Redux. For many of us, this meant we had to choose from a solution that was meant to manage the local State of one widget, Flutter’s Monad or from Redux, which is a solution much more suited to large apps.

The amount of boilerplate needed to set up Redux can be daunting, to say the least. You have your actions, reducers, store, models and some also use an architecture that includes containers.

On a good note, Redux is designed to prevent bugs by making the State immutable and using a data flow that only goes in one direction. However, Redux may do well in synchronous situations but you can run into problems when you start doing things asynchronously. In such cases, there can be serious side effects that make it difficult to devise a solution.

Pros of Redux

  • Circular, unidirectional data flow.
  • Immutability, the State is never mutated outside of the dataflow.
  • In synchronous situations, Redux guarantees the application will behave in a very predictable manner. This makes debugging easy.
  • The State is contained in something called a Store, and you can keep the previous 5 versions of the State in the app, with a list of actions performed. This can give you a record of events that happened just before a crash, and this is something that can be priceless when debugging!

Cons of Redux

  • A lot of boilerplate.
  • A paradigm of having reducers and actions is mind-bending to many people.
  • Redux well in synchronous situations but there can be serious side effects when you start doing things asynchronously.

There are other ways of handling State you might be using. RxVMS by Thomas Burkhart is another solution that implements Streams that has been gaining popularity and it seems there are more approaches coming out every month.

Read more how to organize your Flutter app by Scott Stoll

You can stalk the author on Twitter at @scottstoll2017, or LinkedIn.

Codemagic CI for Flutter