State Management With Provider

The Flutter team recommends several state management packages and libraries. Provider is one of the simplest to update your UI when the app state changes. By Michael Katz.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Inspecting Provider Widgets

As a quick aside, since the Providers are Widgets, you can inspect them in the Flutter Inspector as part of the Widget Tree. In Android Studio, it's accessed from the View ▸ Tool Windows ▸ Flutter Inspector menu.

The Flutter Inspector showing the MultiProvider tree

As you can see from the resulting widget tree, MultiProvider nests its providers array as children within each other, in order. Thus, it's functionally the same as if you nested these yourself. But it still produces easier to read code without all that indentation.

Forwarding Notifications

Do you remember the forwarding of Exchange's change notifications in the CurrencyListViewModel in order to get the CurrencyList to reload? It's time to clean that up with some additional Provider types.

You can use a ProxyProvider to mediate the exchange of notifications between the two model objects: Exchange to CurrencyListViewModel. Think of a ProxyProvider as a transformer for providers that can take the updates from one or more providers and output with a new provider.

To do that, open lib\ui\view_models\currency_list_viewmodel.dart. Then, delete the body from the CurrencyListViewModel constructor.

Next, add the following method at //TODO: add exchangeUpdated():

void exchangeUpdated() => notifyListeners();

This helper will forward the notifications to update the view.

Next, open lib\ui\views\currency_list.dart. You'll need to make a few adjustments to build() so replace it with:

@override
Widget build(BuildContext context) {
  // 1
  return MultiProvider(
    providers:
    [
      // 2
      ChangeNotifierProvider.value(value: exchange),
      // 3
      ChangeNotifierProxyProvider<Exchange, CurrencyListViewModel>(
          create: (context) => CurrencyListViewModel(
              // 4
              exchange: Provider.of<Exchange>(context, listen: false),
              favorites: favorites,
              wallet: wallet
          ),
          // 5
          update: (_, __, vm) => vm!..exchangeUpdated(),
      ),
    ],
    // 6
    child: Consumer<CurrencyListViewModel> (
      builder: (_, model, __) => buildListView(model),
    ),
  );
}

The updated method takes advantage of some concepts you saw in the last section and introduces some new ones as well:

  1. The MultiProvider is used here since there are two providers.
  2. Using ChangeNotifierProvider.value allows you to make a ChangeNotifierProvider from an existing ChangeNotifier object. The exchange object's lifecycle is managed outside of this file, so it shouldn't be created by the provider.
  3. ChangeNotifierProxyProvider is a specialized form of ProxyProvider for ChangeNotifiers. This declares that ChangeNotifier for CurrencyListViewModel will be the proxy for an Exchange provider.
  4. The create block is used to instantiate the new value for the provider. Provider.of<Exchange>(context) grabs the value from the Exchange provider in the supplied BuildContext. This is the function-form of the Consumer widget. The listen: false is important inside a create block to prevent the widget tree from updating due to the state changes caused by the init.
  5. The update block is called in response to change notifications from the proxied object, the exchange. This is where the updates are passed along from the change notifications.
  6. This time, there is only a single Consumer. This child widget is the same as it was before.

Rebuild and run, and the currency list should still reload automatically once the data loads!

First tab with currencies correctly loaded

The updated code still forwards the exchange updates to the view through the viewmodel. The advantage of using ProxyProvider is that you don't have to add and remember to clean up listeners. The type definitions lay out the dependency.

Congratulations, you've completed this tutorial.

Where to Go From Here

There are a few areas to explore from this point. One next step is to implement a version of CurrencyService that connects to a live API such as Open Exchange Rates. Specialized providers such as FutureProvider and StreamProvider are useful for listening to network events.

Provider is a relatively simple state management package that enables app architectures like MVVM (model-view-viewmodel) as used in this app. The Flutter documentation covers several other state management patterns such as Redux, BLoC and MobX.

Of course, to go into more depth on this topic check out the State Management Video course and The Flutter Apprentice.

If you have any questions or comments, please join the forum discussion below!