Provider—Easy State Management In Flutter
Tags: Software Engineering, Cross Platform Mobile App, Mobile App Development, Programming, Flutter

There are many different methods to manage state in Flutter. The recommended approach according to the Flutter documentation is to use Provider. This article addresses the basics of using Provider in your flutter application. If you are coming from a different mobile development background you may find it easier to get started with a state management solution you are more familiar with (Redux, BLoC, etc.).

Why do we need state management? Why is this such an important topic to address?

As your application scales, you will be faced with the need to share state amongst widgets or even pages. Sure we could do this through a series of callbacks, but Provider handles a lot of this heavy lifting for us.

What is Provider?

Provider lets you manage a value (can be a data model object) through its lifecycle by letting you define create and dispose methods. create and dispose work similar to State.initState and State.dispose . With Provider, you can provide this value anywhere in the widget tree of your application.

Provider<DataModel>(

create: (context) => DataModel(),

dispose: (context, value) => value.dispose(),

child: YourWidget(),

)

An advantage to using Provider is that create is lazily called, meaning it is only called the first time the value of the Provider is read. This feature allows developers to instantiate Providers at very high levels and not worry about all the create calls deteriorating performance when the Provider may not even be used. If needed this be disabled through lazy: false .

Sample State Management App

Let’s build on the default flutter app to show how you can manage state better between widgets and between pages. We will change the default counter app so it also displays the timestamp of when the + button was pressed instead of incrementing a value. We will first display this on the same page and display all timestamps within the past hour on a new page.

Sample state management app to be made.

First, we need to add our Provider dependency to the pubspec.yaml file.

provider: ^4.3.2+2

Using Provider we can pass state between widgets by bringing state up to where the Provider was initialized. The Provider package contains a lot of classes that are very useful for flutter development. You can check out the full Provider library documentation here. To implement this basic application with Provider state management, there are three basic classes we need to understand:

ChangeNotifier

Included in Flutter SDK

Provides a change notification to its listeners through notifyListeners() method

method You can subscribe to a ChangeNotifier to get up to date information when required

ChangeNotifierProvider

Included in Provider library

Allows you to provide a ChangeNotifier class to all of its descendants

Consumer

Included in Provider library

Consumes data from a Provider and passes the value to its builder

When the value of the Provider changes, the builder will rebuilds

If UI does not need to be rebuilt we can use Provider.of instead

Initialize Provider

First, we need to create a ChangeNotifier class to hold and manage our data. This class contains a List<DateTime> with our data and a function addDateTime to be called each time the counter button is pressed.

Then we want to initialize the Provider above our Material App using the ChangeNotifier we just wrote. This way the value is available at all levels of the widget tree to be consumed.

State Management Between Widgets

Now that our Provider is ready to be consumed, we can share states between our widgets. In our sample application, the state of our ListView to display the data is determined by the FloatingActionButton . In a scenario where we're not using Provider, this could have easily been done by calling setState on a List<DateTime> object to notify to the ListView to rebuild.

We can do this using a Consumer widget that is wrapped over our ListView , so ListView gets rebuilt on the button press action of the FloatingActionButton .

The onPressed of the button calls the function we wrote in CounterNotifier (line 23 -24 below) , which includes a notifyListeners() call.

Note that we are using listen: false , this is because we are not interested in listening to the change of state, but rather just want to invoke the addDateTime function.

State Management Between Pages

When the ChangeNotifierProvider is at the top of the widget tree, accessing the Provider is no different from how it's done for widgets as shown above.

However, if there is a place where you need to initialize your Provider lower down in the widget tree, this Provider is not available to new routes/pages pushed on.

In other terms, if the Provider you are using on the current page was initialized on this page, it is not available on the next screen you pop on to be consumed. You may do this in your application if you are creating a Provider based on some user action and can not initialize Provider above your MaterialApp . Let's explore this using the widget tree below.

ChangeNotifierProvider below MaterialApp in widget tree.

As you can see this is a problem since SecondPage would not be able to find the Provider. In order to make the Provider available on the new page( ChangeNotifierProvider<CountNotifier> exists above SecondPage in the widget tree), we need to update the builder function of MaterialPageRoute . We create another Provider right above the new page, see lines 27–29 below.

Summary

Using Provider for the simple app shown above is overkill in my opinion and does not provide much value. But as an app expands it is a good tool to use to simplify and handle states better. There are many other useful classes in the Provider library that can help with the development of your application.

If you are interested in a video explanation check out this recording from Google I/O’ 19.

Credit: www.giphy.com

All of the code from this article can be viewed at the Github link below, in a sample application created by Xenon Labs.