Andrea Bizzotto
Jul 9 ยท 5 min read

This article is a write-up of the highlights in this video ๐Ÿ‘†, where we compare different state management techniques.

As an example, we use a simple authentication flow. This sets a loading state while a sign-in request is in progress.

For simplicity, this flow is composed of three possible states:

These are represented by the following state machine, which includes a loading state and an authentication state:

When a sign-in request is in progress, we disable the sign-in button and show a progress indicator.

This example app shows how to handle the loading state with various state management techniques.

Main Navigation

The main navigation for the sign-in page is implemented with a widget that uses a Drawer menu to choose between different options:

The code for this is as follows:

This widget shows a Scaffold where:

  • the AppBarโ€™s title is the name of the selected option
  • the drawer uses a custom built MenuSwitcher
  • the body uses a switch to choose between different pages

Reference flow (vanilla)

To enable sign-in, we can start with a simple vanilla implementation that doesnโ€™t have a loading state:

When the SignInButton is pressed, we call the _signInAnonymously method.

This uses Provider to get an AuthService object, and uses it to sign-in.


  • AuthService is a simple wrapper for Firebase Authentication. See this article for more details.
  • The authentication state is handled by an ancestor widget, that uses the onAuthStateChanged stream to decide which page to show. I covered this in a previous article.


The loading state can be added to the previous implementation by:

  • Converting our widget to a StatefulWidget
  • Declaring a local state variable
  • Using it inside our build method
  • Updating it before and after the call to sign in.

This is the resulting code:

Top Tip: Note how we use a finally clause. This can be used to execute some code, whether or not an exception was thrown.


The loading state can be represented by the values of a stream inside a BLoC.

And we need some extra boilerplate code to set things up:

In a nutshell, this code:

  • Adds a SignInBloc with a StreamController<bool> that is used to handle the loading state
  • Makes the SignInBloc accessible to our widget with a Provider/Consumer pair inside a static create method.
  • Calls bloc.setIsLoading(value) to update the stream, inside the _signInAnonymously method
  • Retrieves the loading state via a StreamBuilder, and uses it to configure the sign-in button.

Note about RxDart

BehaviourSubject is special stream controller that gives us synchronous access to the last value of the stream.

As an alternative to BloC, we could use a BehaviourSubject to keep track of the loading state, and update it as needed.

I will update the GitHub project to show how to do this.


A ValueNotifier can be used to hold a single value, and notify its listeners when this changes.

This is used to implement the same flow:

Inside the static create method, we use a ChangeNotifierProvider/Consumer with a ValueNotifier<bool>. This gives us a way to represent the loading state, and rebuild the widget when it changes.

ValueNotifier vs ChangeNotifier

ValueNotifier and ChangeNotifier are closely related.

In fact, ValueNotifier is a subclass of ChangeNotifier that implements ValueListenable<T>.

This is the implementation of ValueNotifier in the Flutter SDK:

So, when should we use ValueNotifier vs ChangeNotifier?

  • Use ValueNotifier if you need widgets to rebuild when a simple value changes.
  • Use ChangeNotifier if you want more control on when notifyListeners() is called.

Note about ScopedModel

ChangeNotifierProvider is very similar to ScopedModel. In fact these pairs are almost equivalent:

  • ScopedModel โ†”๏ธŽ ChangeNotifierProvider
  • ScopedModelDescendant โ†”๏ธŽ Consumer

So you donโ€™t need ScopedModel if you are already using Provider, as ChangeNotifierProvider offers the same functionality.

Final comparison

The three implementations (setState, BLoC, ValueNotifier) are very similar, and only differ in how the loading state is handled.

Here is how they compare:

  • setState โ†”๏ธŽ least amount of code
  • BLoC โ†”๏ธŽ most amount of code
  • ValueNotifier โ†”๏ธŽ middle ground

So setState works best for this use case, as we need to handle state that is local to a single widget.

You can evaluate which one is more suitable on a case-by-case basis, as you build your own apps ๐Ÿ˜‰

Bonus: Implementing the Drawer Menu

Keeping track of the currently selected option is also a state management problem:

I first implemented this with a local state variable and setState, inside the custom drawer menu.

However, the state was lost after sign-in in, because the drawer was removed from the widget tree.

As a solution, I decided to store the state with a ChangeNotifierProvider<ValueNotifier<Option>> inside the LandingPage:

Here, the StreamBuilder controls the authentication state of the user.

And by wrapping this with a ChangeNotifierProvider<ValueNotifier<Option>>, Iโ€™m able to retain the selected option even after the SignInPageNavigation is removed.

In summary:

  • StatefulWidgets donโ€™t remember their state after they are removed.
  • With Provider, we can choose where to store state in the widget tree.
  • This way, the state is retained even when the widgets that use it are removed.

ValueNotifier requires a bit more code than setState. But it can be used to remember the state, by placing a Provider where appropriate in the widget tree.

Source code

The example code from this tutorial can be found here:

All these state management techniques are covered in-depth in my Flutter & Firebase Udemy course. This is available for early access at this link (discount code included):

Happy coding!

For more articles and video tutorials, check out Coding With Flutter.

Iโ€™m @biz84 on Twitter. You can also see my GitHub page. Did you like this article? Then smash that clap ๐Ÿ‘ button! It makes me feel awesome so I can write more about Flutter. ๐Ÿ˜Ž

Coding with Flutter

Learn to build iOS and Android apps with Dart and Flutter

Andrea Bizzotto

Written by

iOS, Flutter Developer & Blogger โ– โ– โ– Open Source โ– Watching #ClimateChange

Coding with Flutter

Learn to build iOS and Android apps with Dart and Flutter

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and weโ€™ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium โ€” and support writers while youโ€™re at it. Just $5/month. Upgrade