State management is a hot topic in the Flutter community. You have the default StatefulWidget and then there are literally hundreds of different libraries to choose from. This article will cut through the noise and, once and for all, explain the principles which are valid across many state management solutions. You're also going to see examples of some of the most popular patterns and libraries.

The drawbacks of coupling and mutability

Whether you use StatefulWidget, ChangeNotifier, MobX Store or even a ReactiveModel from states_rebuilder, you couple the fields which hold state (e.g. isLoading boolean) with the class that's responsible for mutating the state. In other words, you usually have only one set of fields per ChangeNotifier / other view model of your choice.

Many people (myself included in the past) use the term provider to describe state management with ChangeNotifier. All of this stems from the unfortunate wording at Google IO 2019. The truth is, that provider just "provides" anything you want down the widget tree and it has nothing to do with state management. You can watch the author of the provider package explain this once and for all in this video.

This single set of fields forces you to do crazy hacks for simple things like an initial or "empty" state - will you create a separate isInitial field...

my_change_notifier.dart

class MyChangeNotifier extends ChangeNotifier {
  bool _isInitial = true;
  bool get isInitial => _isInitial;

  bool _isLoading = false;
  bool get isLoading => isLoading;

  MyEntity _result;
  MyEntity get result => _result;

  String _errorMessage;
  String get errorMessage => _errorMessage;

  void getData() {
    // Bunch of code here
  }
}

...or will you set all the fields to null?

my_change_notifier.dart

class MyChangeNotifier extends ChangeNotifier {
  // Do null checks in the UI to find out if the state is "initial"
  bool _isLoading;
  bool get isLoading => isLoading;

  MyEntity _result;
  MyEntity get result => _result;

  String _errorMessage;
  String get errorMessage => _errorMessage;

  void getData() {
    // Bunch of code here
  }
}

It doesn't matter, either one is a bad choice.

As if that wasn't enough, the ChangeNotifier is also very bloated and you also lose out on the benefits of immutable data like easily achievable undo/redo. Imagine how you'd store the history of state changes if you can update any of the fields independently from one another. Not a very soothing idea to think about indeed.

Bloc or Redux in Flutter

These drawbacks are the reason why people like to use Redux or BLoC for state management despite the fact that these solutions come with drawbacks of their own - boilerplate. More on that later ?

The basic premise of these state management packages is to separate the inputs, logic and outputs into self-contained classes/functions. This mitigates all the issues outlined when we talked about ChangeNotifier.

Overcoming limitations of mutable data

#1 The first problem we faced with coupled and mutable solutions was that representing an initial or "empty" state is not easily achievable - you have to choose between bad and worse. Since BLoC can separate the states into multiple classes instead of putting everything into one pile of fairly unrelated fields, representing an initial state has never been easier.

I'll focus on BLoC but similar concepts are applicable to Redux as well (except for the fact that Redux has the concept of "single source of truth" for the entire app, whereas there can be multiple BLoCs).
Also, I'll stick to regular classes and inheritance but you can just as easily use freezed unions.
If the acronym BLoC gives you shivers because you've tried to implement it yourself from scratch with Streams, learn about the Bloc package first before going further.

my_state.dart

@immutable
abstract class MyState {}

// value equality override is needed but it's omitted here
// (that's why you should use Freezed)
class Initial extends MyState {}

class Loading extends MyState {}

class Success extends MyState {
  final MyEntity result;

  Success(this.result);
}

class Error extends MyState {
  final String message;

  Error(this.message);
}

The UI will receive only one of these states at a point of time. You don't need to worry about null checking everything or looking for the value of particular field such as isInitial. All you do is check if state is Initial or state is Success and go from there. 


#2 The second limitation, bloatedness, stems from the fact that a single ChangeNotifier or MobX Store class is responsible for everything from holding the state to performing logic.

You've already seen how states are separated into individual classes. The same goes for events, which are the "methods" of a Bloc - they control what's going to happen next. Of course, the component where the business logic happens (BLoC) is a separate class as well. Sure, there may be more code in absolute terms but its complexity is never overwhelming because it's spread out across classes (and files, usually).

my_event.dart

@immutable
abstract class MyEvent {}

class GetData extends MyEvent {}

my_bloc.dart

class MyBloc extends Bloc<MyEvent, MyState> {
  @override
  MyState get initialState => Initial();

  @override
  Stream<MyState> mapEventToState(
    MyEvent event,
  ) async* {
    if (event is GetData) {
      yield Loading();
      // Bunch of code here
    }
  }
}

#3 The last pressing limitation was the inability to implement undo/redo functionality. Well, since the states are emitted only one at a time, there's nothing simpler than storing the states in a stack/list. You don't need to track the individual fields, you just keep record of the MyState subclasses which are emitted from the Stream.

my_bloc.dart

class MyBloc extends Bloc<MyEvent, MyState> {
  ...
  final stateHistory = <MyState>[Initial()];

  @override
  void onTransition(Transition<MyEvent, MyState> transition) {
    stateHistory.add(transition.nextState);
  }
}

State management is about tradeoffs

You can surely see that BLoC introduces quite a lot of boilerplate but stating this fact without any context is not doing this state management library a service. That's like saying that driving a car adds difficulties to your life without stating any of the benefits. Sure, you need to shell out money for the car and you need to take care of it, but at the same time, it allows you to get where you want much more quickly.

If you want to go across the street, hopping into a car would be crazy. If you're going to a destination which is 200 kilometers (124.27 miles ?) away, you'd be weird if you insisted on going by foot.

Similarly, if you're building an app which is more for display than for functionality, going with the least boilerplatey option makes sense. Building a complex production-grade app requires different solutions altogether.

The benefits of Bloc

The Bloc library provides very good ? tooling and compared to other state management solutions that use Streams, it's a pure gem.

  • Reactive out of the box
    • Bloc is a subclass of Stream, so you know it integrates nicely with just about anything you throw at it without any complications. Examples include:
      • map a Firestore real-time Stream of data into a Stream of states.
      • Apply RxDart operators on Stream<State> and Stream<Event>. For example, debounceTime is useful for auto-search where you want to start searching for a query only after the user has stopped typing for a while.
  • Analytics without any additional work
    • With the help of the BlocDelegate, you can know about any action the user takes in any BLoC without cluttering up your codebase with analytics code.
  • Testing Streams is easy, testing Blocs is even easier
    • Dart is built with Streams in mind and testing if a particular sequence of states has been emitted is simple with stream matchers.
    • Why would you leave it at just the matchers though when you can use the bloc_test package which, in my opinion, makes tests fun to write?
  • BIASED OPINION: The best documentation ever

So, if you want to have the benefits of immutable state, closely work with Streams whether for the purposes of testing or integration with other code, observe state changes happening across the app from a single place ( BlocDelegate ) easily to allow for pain-free analytics and at the same time keep your code as simple as possible, BLoC is truly the best available option out there.

Additionally, you don't have to use just one state management package. While you may use Bloc for managing authentication state, nothing stops you from from keeping a dynamically changing color of a button in a good old StatefulWidget or even a ChangeNotifier. My rule is to manage simple state with simple solutions.

StateNotifier - lean & immutable

But what if you don't need anything that working with Streams has to offer but you still want to keep your state immutable? Is there a crossbreed of Bloc and ChangeNotifier?

Many people who are using Bloc would gladly trade its boilerplate for leaner code while not losing out on immutability. At the same time, people who swear by ChangeNotifier may want to reap the benefits of immutable data but they're hesitant to switch to Bloc.

This is exactly where the StateNotifier steps in. It inherits the best features of Bloc and ChangeNotifier while compromising only the Stream-specific features like the ability to use RxDart operators.

The state_notifier package is authored by Rémi Rousselet, the creator of provider, so you know they will work together nicely. For example, if you're using a ProxyProvider for injecting dependencies into a ChangeNotifier, you'll no longer need to do that if you migrate to StateNotifier.

An in-depth tutorial on how to use state_notifier in Flutter is coming shortly. Subscribe to the newsletter if you don't want to miss it and also to be informed about weekly Flutter news and resources.

StateNotifier has only a single way to communicate data back to the UI - the state field, much like a Bloc has a single Stream of states. This means that the state classes used for Bloc are just as valid for use with a StateNotifier. As a refresher:

my_state.dart

@immutable
abstract class MyState {}

// equality override is needed but it's omitted here
// (that's why you should use Freezed)
class Initial extends MyState {}

class Loading extends MyState {}

class Success extends MyState {
  final MyEntity result;

  Success(this.result);
}

class Error extends MyState {
  final String message;

  Error(this.message);
}

On the contrary to the Bloc package, state_notifier doesn't have the concept of events. Instead, you create regular methods just like with a ChangeNotifier

my_state_notifier.dart

class MyStateNotifier extends StateNotifier<MyState> {
  // initial state gets passed to the super constructor
  MyStateNotifier() : super(Initial());

  void getData() {
    state = Loading();
    // Bunch of code here
  }
}

Best of both worlds?

Since there are no streams, there's substantially less boilerplate than if you had used Bloc. For many people who are on the fence between Bloc and ChangeNotifier, the StateNotifier may be the just the right choice.

If, however, you'd like to use anything Stream-specific which I outlined above (like RxDart operators or great testing experience), then I'd rather stick with Bloc.

Bottom line

I know it's insane. An article about state management which has a bottom line ? Seriously though, the heading is just a joke. I really don't have just one recommendation.

In this post, we explored mutable and immutable state management solutions for Flutter. We focused on ChangeNotifier for the mutable part, but all of the other ones are basically the same in their concept. Then we explored the world of immutable state mostly through the lens of Bloc and its Stream-free alternative, the StateNotifier.

If you like mutating your state and you swear by MobX, that's great. Keep using it. If you like Redux, don't be discouraged by people who are badmouthing it. ChangeNotifier is also great for plenty of simpler apps. The Bloc library is a great choice if you want to build a robust application. State Notifier is awesome if you want immutability without needing to worry about Streams.

Yes, it has ended just like any other state management tutorial but there's one difference. Hopefully, by understanding the main dividing factors of the different state management solutions, you can now make an educated choice for your next Flutter app.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • Hello, thanks for great staff. But I have question about my_change_notifier.dart example: Why could not we encapsulate state into MyState based hierarchy of classes, similarly to one at my_state.dart? It would both solve issue of initial state and have all state modification clean using something like MyState updateState (Event myEvent, MyState curState)?

      • I’m late to the party, but is that why Cubits were created? As an alternative within the BLoC universe to state_notifier?
        If so, do you have an opinion on which is “better” – Cubits or state_notifiers?

  • BLoC is better than ChangeNotifier approach only in case when you need an undo/redo functionality. That’s no brainer.

    But as for the rest, it’s not.

    1. The issue of “initial or “empty” state”: Why in the world do you use “isInitial” and such fields? Whereas you can have a `MyState state;` field and a corresponding `enum MyState {Initial, Loading, Loaded, Etc,}.`

    2. Bloated class issue: you’re not creating all your page in one dart file, right? The same thing with ChangeNotifier – your page may consist of some parts, make a reasonable number of parts and ChangeNotifiers (ViewModels) for them and you’re good.

    and as a bonus: if you use Provider + ChangeNotifier you may want to use ProxyProvider to inject your services inside ChangeNotifiers but let’s be honest – get_it service locator makes it better and more cleaner.

    Happy coding and thank you for the article!

    • You’re totally right about the enum for the state, I should’ve put it in the tutorial. Still, nothing beats “physically” limiting the fields I can access by using separate state classes with Bloc/StateNotifier.

      As for the second point, I create multiple BLoCs for one page too.

      Couldn’t agree more about get_it.

      In the end, it’s about preferences. I truly enjoy the aspects of immutability and close integration with Streams which BLoC provides. At the same time, I see why many people would rather use ChangeNotifier or MobX.

  • You should write a book, and have people pay for it. I am serious. This is such great quality information, we don’t get elsewhere.

  • I do not even know how I ended up here, but I thought this post was great. I do not know who you are but certainly you’re going to a famous blogger if you are not already 😉 Cheers!

  • Thanks, I have recently been looking for info about this subject for a while and yours is the greatest I have discovered so far. However, what in regards to the bottom line? Are you certain in regards to the supply?

  • Somebody essentially lend a hand to make significantly articles I’d state. That is the very first time I frequented your website page and up to now? I surprised with the research you made to make this actual submit amazing. Wonderful task!

  • Hi, i think that i saw you visited my web site thus i came to ?eturn the favor텶 am attempting to find things to improve my web site!I suppose its ok to use some of your ideas!!

  • Hello, Neat post. There’s an issue together with your site in internet explorer, would check this텶E still is the marketplace chief and a large element of other folks will leave out your magnificent writing due to this problem.

  • I do agree with all the ideas you have introduced on your post. They are very convincing and will definitely work. Still, the posts are very short for newbies. May just you please prolong them a little from subsequent time? Thank you for the post.

  • you are in reality a good webmaster. The website loading velocity is amazing. It sort of feels that you’re doing any distinctive trick. Also, The contents are masterwork. you have done a fantastic job in this topic!

  • I do believe all the ideas you’ve presented for your post. They are really convincing and will certainly work. Nonetheless, the posts are too short for novices. May just you please lengthen them a little from subsequent time? Thanks for the post.

  • Its like you read my mind! You appear to know so much about this, like you wrote the book in it or something. I think that you can do with a few pics to drive the message home a little bit, but other than that, this is fantastic blog. A great read. I’ll certainly be back.

  • I loved as much as you will receive carried out right here. The sketch is tasteful, your authored subject matter stylish. nonetheless, you command get got an edginess over that you wish be delivering the following. unwell unquestionably come further formerly again as exactly the same nearly very often inside case you shield this hike.

  • Somebody essentially help to make significantly articles I’d state. This is the first time I frequented your web page and up to now? I surprised with the research you made to make this actual post incredible. Fantastic job!

  • Somebody essentially lend a hand to make significantly articles I’d state. That is the very first time I frequented your website page and up to now? I surprised with the research you made to make this actual submit amazing. Wonderful task!

  • Fantastic beat ! I would like to apprentice while you amend your web site, how could i subscribe for a blog site? The account helped me a acceptable deal. I had been a little bit acquainted of this your broadcast offered bright clear concept

  • Simply desire to say your article is as surprising. The clearness in your post is simply excellent and i could assume you are an expert on this subject. Fine with your permission let me to grab your feed to keep up to date with forthcoming post. Thanks a million and please carry on the gratifying work.

  • Maximize Water Efficiency with Bwer Pipes’ Irrigation Solutions: At Bwer Pipes, we understand the importance of water conservation in Iraqi agriculture. That’s why our irrigation systems minimize water wastage while delivering precise hydration to your crops. Experience the difference with Bwer Pipes. Visit Bwer Pipes

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >