Flutter tutorials and courses by Andrea Bizzotto

Flutter State Management Basics and Useful Resources

Flutter state management is a broad subject and it can be hard to make sense of it all.

In this tutorial I'll help you understand the basics by explaining what is state and the difference between local and shared/global state.

And I'll give you an overview of the built-in widgets that you can use to manage state in Flutter, along with links to the best resources from the official Flutter documentation.

You can go quite far and build non-trivial applications with the built-in Flutter state management widgets. It's worth learning how they work before moving to more complex solutions.

In the next tutorial, I'll show you how to use useful packages such as Freezed, StateNotifier. And I'll also include a complete guide to the Riverpod package. Along the way, I will cover important state management principles and Flutter best practices that you can follow to write high-quality code.

But before we can run, we need to learn how to walk.

For that, we need to understand what is state and how Flutter apps are built.

What is state?

Flutter apps are built with a declarative programming model.

This means that widgets define their UI by overriding the build() method, which is a function that converts state to UI:

UI = f(state)

In this context, the Flutter documentation defines state as:

whatever data you need in order to rebuild your UI at any moment in time

We could also say that the UI is all about how your application looks like. And the state is about how your application behaves.

In principle, this is a simple concept:

state => UI

What is complex is that there are many different ways of transforming state into UI, with different pros and cons.

Not only that, but there are two different kinds of state: local state and global state.

Local vs Global State

As we know, we can compose Flutter widgets into a widget-tree that represents our application's UI:

Widget tree for the counter app
Widget tree for the example Flutter Counter app

Sometimes you have state that can be self-contained inside a single widget. That state is known as local or ephemeral state and Flutter offers some built-in tools such as setState and StatefulWidget to deal with this.

If you have some simple state that only affects the behaviour of a single widget, StatefulWidget is all you need. This video from the official Flutter channel explains how to use it:

On the other hand, when state needs to be shared across multiple widgets or even the entire app, you're dealing with shared / global app state.

Sharing state across multiple widgets is where things get interesting and there are many different techniques to do this. So let's review what Flutter offers out-of-the-box:

InheritedWidget

You can use Flutter's built-in InheritedWidget to share state across multiple widgets, as explained in this video:

InheritedWidget is all about making some data or state available to multiple widgets via scoped access. For example, inside your widgets you can call Navigator.of(context) to access the main Navigator and this uses InheritedWidget under the hood:

Scoped access with InheritedWidget
Scoped access via InheritedWidget

Implementing your own InheritedWidget subclasses is not easy and quite error-prone. For this reason, the provider package was introduced.

ValueNotifier & ChangeNotifier

Flutter includes a ValueNotifier class as a way to store your state outside your widgets. You can use it with a ValueListenableBuilder widget to update the UI when the state changes. This is best explained here:

Here's an example showing how to use ValueNotifier and ValueListenableBuilder together:

final valueNotifier = ValueNotifier<int>(42); ValueListenableBuilder<int>( valueListenable = valueNotifier, // called when the value changes builder: (context, value, _) { return Text('Value is $value') } );

Flutter also comes with ChangeNotifier, which is the "cousin" of ValueNotifier. For a practical explanation of how to use it, see this page.

FutureBuilder & StreamBuilder

Many asynchronous APIs use Futures and Streams to notify your app when new data is available.

Their difference is that a Future produces a single asynchronous value, while a Stream can produce many asynchronous values over time.

Both Futures and Streams are part of the Dart SDK. My Complete Dart Language course covers them in great detail.

You can use Flutter's FutureBuilder widget to decide what widget to show depending on the state of the Future (loading, data, or error):


Similarly, use Flutter's StreamBuilder widget to rebuild your UI when a stream emits new data:

When loading asynchronous data from a Stream-based API, it's good practice to check for these UI states: data, no data, error, loading.

StreamBuilder supports this but it is quite clunky to check for data or errors inside the builder's snapshot:

final stream = Stream.fromIterable([21, 42]); StreamBuilder<int>( stream: stream, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.active) { if (snapshot.hasData) { return MyWidget(snapshot.data); // data } else if (snapshot.hasError) { return MyErrorWidget(snapshot.error); // error } else { return Text('No data'); // no data } } else { return CircularProgressIndicator(); // loading } } )

3rd party packages such as flutter_bloc, Provider, and Riverpod offer alternatives to StreamBuilder that are much easier to use.

Using Flutter's built-in widgets

You can go quite far with the built-in StatelessWidget, StatefulWidget, and InheritedWidget classes. You can use ChangeNotifier or ValueNotifier to manage your state, or FutureBuilder and StreamBuilder when reading data from asynchronous APIs.

This official guide shows how to put things together using ChangeNotifier and ChangeNotifierProvider to build a simple shopping cart application:

Wrap Up

The built-in widgets we covered are at the basis of state management in Flutter. Over 40 state management packages now exist, but they all build on the same principles and foundations.

So take the time to understand the basics by exploring the resources I shared.

You can complete the Flutter beginner codelabs to get some practice.

And if you want to dig deeper, check out my next tutorials:

Happy coding!

Want more?

Fast-track your Flutter learning with over 40 hours of in-depth content.