Flutter State Management with Provider

Flutter State Management with Provider

2019-12-14T23:46:37.997Z

Installation

To install Provider, depend on provider in pubspec.yaml:

dependencies:
  provider:

Use

Concept Purpose
ChangeNotifier Contains state, notifies listeners
ChangeNotifierProvider Provides state
Consumer Read/updates state, rebuilds

1. ChangeNotifier

Create a class model that contains state.

import 'package:flutter/foundation.dart' // contains ChangeNotifier

class CartModel extends ChangeNotifier { // enables notifyListeners()
    final List<Item> _items = [];
    int get totalPrice => _items.length * 42;

    void add(Item item) {
        _items.push(item);
        notifyListeners();
        // causes listeners to rebuild on state change
    }
}

2. ChangeNotifierProvider

Place the model in the tree, to provide state to widgets below.

Note: The "top widget" is the common ancestor in a section of the widget tree whose widgets need state.

To place the model, locate the top widget and wrap it in ChangeNotifierProvider<T>. That T is your class model. This provides state data to anywhere in the widget tree, from the wrapped widget down.

In wrapping the top widget, ChangeNotifierProvider<T> takes two arguments: builder, an invoked function instantiating the state model class, and child, the top widget being wrapped.

ChangeNotifierProvider<CartModel>(
    builder: (context) => CartModel(),
    child: MyApp(),
),

3A. Provider.of

Read/update the class model from any widget below in the tree.

Down the widget tree, some widgets ("consumers" or "listeners") need to access state in order to read it and rebuild to reflect state changes, and some widgets ("producers") need to access state to update it.

Provider.of<T> evaluates to the closed ancestor widget whose type is the state model class, i.e. Provider.of<T> evaluates to the instance of the state model class with all its properties and methods.

FlatButton(
  onPressed: () {
    final model = Provider.of<CartModel>(context);
    print(model.totalPrice); // read state
    model.add(item); // update state
  },
);

To rebuild or not to rebuild

By default, Provider.of<T> triggers a rebuild of the widget, so it is used for a consumer that needs to show updated state (think a counter in a cart), i.e. the widget is auto-registered as a listener with an implicit default of listen: true. The rebuild affects also all its descendants.

FlatButton(
  onPressed: () {
    final cartModel = Provider.of<CartModel>(context);
    cartModel.add(item); // triggers rebuild
  },
);

To override the default and prevent it from rebuilding and thus to simply update the value, set listen: false. This is useful if the widget is a producer that only updates state without showing the update (think a button to remove items from a cart), to prevent wasteful rebuilds.

FlatButton(
  onPressed: () {
    final cartModel = Provider.of<CartModel>(context, listen: false);
    cartModel.clearAll(); // does NOT trigger rebuild
  }
);

3B. Consumer<T>

Read/update the class model from any widget below in the tree.

Consumer is an alternative to Provider.of<T>. Consumer is useful if you need access to the BuildContext.

Down the widget tree, some widgets ("consumers" or "listeners") need to access state to rebuild to reflect state changes, and some widgets ("producers") need to access state to update it. To give them access to state, wrap them with Consumer<T>. That T is the state class model. The consumer will rebuild whenever the class model calls notifyListeners().

In wrapping the consumer, Consumer<T> takes a builder argument, an anonymous function with three arguments, the second argument being an instance of the class model. Disregard context and child.

Consumer<CartModel>(
  builder: (context, cartModel, child) {
    return RaisedButton(
      onPressed: () {
        print(cartModel.totalPrice); // read state
        cartModel.add(item); // update state
      }
    )
  }
);