State Management With Provider | Kodeco

Update notes: Mike Katz updated this tutorial for Flutter 3. Jonathan Sande wrote the original.

Through a widget-based declarative UI, Flutter makes a simple promise; describe how views are created for a given state of the app. When the UI needs to change to reflect a new state, the toolkit takes care of determining what needs to be built and when. For example, when a player scores points in the game, the “current score” label text should be updated to reflect the new score status.

The concept is called state management contains the coding of when and where to apply state changes. If your app has changes to present to the user, you’ll want to update the relevant widgets to reflect that status. In a demanding environment you can use a method like a setText() or setEnabled() to change widget properties from a callback. In Flutter, you notify related widgets that state has changed so they can be rebuilt.

The Flutter team recommends several state management packages and libraries. giver is one of the simplest to update your UI when the app state changes, which you’ll learn how to use here.

In this tutorial you will learn:

  • How to use Provider with ChangeNotifier classes to update views when your model classes change.
  • Use of MultiProvider to create a hierarchy of providers within the widget tree.
  • Use of ProxyProvider to link the two providers.

Understanding state management is essential to becoming a competent Flutter developer. By signing a Personal Kodeco Subscription, you will have access to Managing State in Flutter. This video course will teach you the basics of state management from scratch.

dash

In this tutorial you will create a currency exchange app, Moola X. This app allows its user to track different currencies and see their current value in their preferred currency. The user can also track how much they have of a particular currency in a virtual wallet and track their net worth. To simplify the tutorial and keep the content focused on the Provider package, the currency data is loaded from a local data file instead of a live service.

Download the project by clicking on Download materials link at the top or bottom of the page. Get the starter app up and running.

The initial launch of the app shows a blank list

You can see that the app has three tabs: an empty currency list, an empty favorites list, and an empty wallet indicating that the user has no dollars. For this app the base currency, which is given by the author’s bias, is the US Dollar. If you want to work in a different base currency, you can update it to lib/services/currency/exchange.dart. Change the definition of baseCurrency whatever you want, like CAD for Canadian Dollars, GBP for British Pounds, or EUR for Euro, etc…

For example, this replacement will set the app to Canadian Dollars:

final String baseCurrency = 'CAD';

Quit and restart the app. The wallet will now show that you are gone Canadian Dollar. When you create the app the exchange rates will be calculated. πŸ™‚

The app is configured for Canadian currency

Convert the app to “USD or whatever currency you want to use.

As you can see, the app doesn’t do much yet. In the next sections you will build the functionality of the app. Using Provider you will dynamically keep updating the UI as user actions change app state changes.

The process is as follows:

  1. The user, or some other process, is running.
  2. The handler or callback code starts a chain of function calls that result in a change of state.
  3. A giver that listening for changes provides the updated values ​​to the listening widgets, or CONSUMPTION that new state value.

Update flow for state change

Once you’ve completed everything in the tutorial, the app will look like this:

First tab with currencies correctly loaded

Providing Change of Status Notifications

The first thing to fix is ​​to load the first tab, so that the view updates when the data comes in lib/main.dart, MyApp creates an instance of Exchange which is the service that loads currency and exchange rate information. When the build() method of MyApp creates the app widget, it calls exchange’s load().

TOMORROW lib/services/currency/exchange.dart. You can see that load() set in a chain of Futures which load data from CurrencyService. The first one Future is the loadCurrencies()shown below:

  Future loadCurrencies() {
    return service.fetchCurrencies().then((value) {
      currencies.addAll(value);
    });
  }

In the block above, when the fetch completes, the completion block updates internally currencies list with new values. Now, there is a change of state.

Next, see the lib/ui/views/currency_list.dart. the CurrencyList widget shows a list of all known currencies in the first tab. Information from Exchange passing CurrencyListViewModel to separate the view and model logic. The view model class then informs the ListView.builder how to make a table.

When the app launches, the Exchangeof currencies the list is empty. So the view model reports that there aren’t any rows to construct for the list view. When its load is complete, the ExchangeThe data updates but there is no way to tell the view that the state has changed. Indeed, CurrencyList itself is a StatelessWidget.

You can get the list to show the updated data by selecting another tab, and then selecting the currencies tab again. When the widget builds a second time, the view model has data ready from the exchange to fill the rows.

First tab with currencies correctly loaded

Manually reloading the view can be a utilize workaround, but it’s not a good user experience; it’s not really in the spirit of Flutter’s state-driven declarative UI philosophy. So, how to make it automatic?

This is where the giver the package comes to help. There are two parts to the package that enable widgets to update on state changes:

  • A giverwhich is an object that manages the state object’s lifecycle, and “gives” it the view hierarchy that depends on that state.
  • A consumerswhich builds the widget tree using the value provided by the provider, and will be rebuilt if that value changes.

For CurrencyList, the view model is the object you need to provide in the list to use for updates. The view model then listens for updates to the data model – the Exchangeand then pass that along with the values ​​for the view widgets.

Before you use it Provider, you should add it as one of the project’s dependencies. A straightforward way to do that is to open the Moolax base directory in the terminal and run the following command:

flutter pub add provider

This command adds the latest version Provider version of the project pubspec.yaml file. It also downloads the package and resolves all dependencies in one command. This saves the extra step of manually searching for the current version, of manually updating pubspec.yaml and then called flutter pub get.

Right now Provider available, you can use it in the widget. Start by adding the following import to the top of the lib/ui/views/currency_list.dart on // TODO: add import:

import 'package:provider/provider.dart';

Next, replace the existing one build() with:

@override
Widget build(BuildContext context) {
  // 1
  return ChangeNotifierProvider<CurrencyListViewModel>(
    // 2
    create: (_) => CurrencyListViewModel(
        exchange: exchange,
        favorites: favorites,
        wallet: wallet
    ),
    // 3
    child: Consumer<CurrencyListViewModel>(
        builder: (context, model, child)
        {
          // 4
          return buildListView(model);
        }
    ),
  );
}

This new approach uses the main concepts/classes from Provider: the giver and consumers. This is done in the following four ways:

  1. A ChangeNotifierProvider a widget that manages the life cycle of a given value. The widget tree content it depends on will be updated when its value changes. This is the specific implementation of Provider who work with ChangeNotifier values. It listens for change notifications to know when to update.
  2. the create block instantiates the view model object so that the provider can manage it.
  3. the child is the rest of the widget tree. Here, a Consumer using the provider for CurrencyListViewModel and passes its given value, the created model object, to builder method.
  4. the builder now back to the same ListView made in the manner of the assistant as before.

As it was done CurrencyListViewModel informs its listeners of the changes, the Consumer gives the new value to its children.

NOTES: In the tutorials and documentation examples, the Consumer often comes as the immediate child of Provider but that is not necessary. The consumer can be placed anywhere within the child tree.

The code is not ready yet, it seems CurrencyListViewModel not a ChangeNotifier. Fix that by unlocking lib/ui/view_models/currency_list_viewmodel.dart.

First, modify the class definition by adding ChangeNotifier as a mixin by changing the line below // TODO: replace class definition by adding mixin:

class CurrencyListViewModel with ChangeNotifier {

Next, add the following body to the constructor CurrencyListViewModel() by replacing the // TODO: add constructor body with:

{
 exchange.addListener(() {notifyListeners();}); // <-- temporary
}

Now the class is a ChangeNotifier. It is given by ChangeNotifierProvider on CurrencyList. It will also listen for exchange changes and send them as well. This last step is only a temporary solution to load the table quickly. You’ll clean it up later when you learn to work with multiple providers.

The final piece to fix compiler errors is addition ChangeNotifier on Exchange. Again, open lib/services/currency/exchange.dart.

At the top of the file, add this import to // TODO: add import:

import 'package:flutter/foundation.dart';

ChangeNotifier is part of Foundation package, so it is available to use.

Next, add it as a mixin by changing the class definition to // TODO: update class definition/code> to:

class Exchange with ChangeNotifier {

Like everyone else CurrencyListViewModelit enables Exchange to allow other objects to listen for change notifications. To send notifications, update the block to complete the loadExchangeRates() by changing the method to:

 Future loadExchangeRates() {
  return service.fetchRates().then((value) {
     rates = value;
     notifyListeners();
  });
}

It adds a call to notifyListeners WHEN fetchRates will be completed at the end of the chain of events started load().

Build and run the app again. This time, when the load is complete, the Exchange inform the CurrencyListViewModel and it will then notify the Consumer on CurrencyList which will then update its children and the table will be redrawn.

Loading the list after notifying the provider

Leave a comment