Flutter Provider

March 18, 2020

The best FLutter state management library: https://pub.dev/packages/provider but have the worst how-to-use document.

Because of the missing document, Flutter official wrote a document for this package Simple app state management to introduce how-to-use.

Usage

Fist need create class mixin with ChangeNotifier class for store state and data in.

Here describe what’s ChangeNotifier.

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

Use MultiProvider wrap the top App build return widget for expose the data from top, that descendant can get this data.

Must expose above the widgets that need to access it, so normally put expose at top is the easiest. If you don’t want pollute the scope, put it down with the necessary widgets.

  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: Consumer<Counter>(
        builder: (context, counter, _) {
          return MaterialApp(
            supportedLocales: const [Locale('en')],
            localizationsDelegates: [
              DefaultMaterialLocalizations.delegate,
              DefaultWidgetsLocalizations.delegate,
              _ExampleLocalizationsDelegate(counter.count),
            ],
            home: const MyHomePage(),
          );
        },
      ),
    );
  }

Providers

There is a different provider.

ChangeNotifierProvider

ChangeNotifierProvider is the most useful provider.

Use this provider to expose data above the widgets which need to access it.

MultiProvider

If more than one class need expose you can use MultiProvider. it has list providers, you can put different provider in it to expose.

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => CartModel()),
        Provider(create: (context) => SomeOtherClass()),
      ],
      child: MyApp(),
    ),
  );
}

Provider

ListenableProvider

ProxyProvider

Builds a value base on other Providers.

Accessor

Provider.of

The simplest accessor.

When you need access model data from logic code not the widget, you can use Provider.of accessor.

Provider.of get model from context, you need use generic specify the type of the model. put the current context to of function, it will return the model instance.

If you want listen model notify and rebuild current widget, you can get model use Provider.of with default listen=true in the build function:

  OauthInfo oauthInfo = Provider.of<OauthInfo>(context);

It always rebuild whole widget which use this build function.

If you just want call some model instance function don’t care data change and no need rebuild widget for show data change, you can get it with listen=false with any function even inside the initState:

  CartModel cartModel = Provider.of<CartModel>(context, listen: false);

Consumer

Consumer is a widget accessor you can use it for just rebuild part of widget even in stateless widget.

Must specify the type of the model you want to access. One Consumer just can access one model class.

Use generic specify the model type you want access: Consumer<CartModel>.

Consumer widget just has two argument: builder and child, the argument builder is a callback function will run when notifyListeners()(in your model) is called. it is the required argument.

callback function get three arguments:

  1. context
  2. instance of the ChangeNotifier model
  3. child

third child is for optimization, if some widget don’t change by model modify, you can set in child avoid this widget rebuild:

return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
        children: [
          // Use SomeExpensiveWidget here, without rebuilding every time.
          child,
          Text("Total price: ${cart.totalPrice}"),
        ],
      ),
  // Build the expensive widget here.
  child: SomeExpensiveWidget(),
);

Try to put Consumer in the deep of the tree, avoid Consumer rebuild no need rebuild widget:

bad:

// DON'T DO THIS
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);

good:

// DO THIS
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

Selector

If just part of data change need rebuild widget, you should use the Selector accessor.

Use It like Consumer but some different:

  1. generic need specify the care data type, so it has two generic type: first is the model type, second is the care data type.
  2. need set the selector function, it get context and instance of the model need return care data.
  3. the builder function will not get the whole model instance just the care data.
    return Selector<ArticleTitles, List<ArticleTitle>>(
        selector: (context, articleTitles) => articleTitles.filterTitles,
        builder: (context, filterTitles, child) {
          print("Selector $this");
          if (filterTitles.length == 0)
            return getBlankPage();
          else
            return ScrollablePositionedList.builder(
              itemCount: filterTitles.length,
              itemBuilder: (context, index) {
                return ArticleTitlesSlidable(
                    articleTitle: filterTitles.reversed.toList()[index]);
              },
              itemScrollController: itemScrollController,
              itemPositionsListener: itemPositionListener,
            );
        });

Author said:Selector now deeply compares the previous and new values if they are collections. but it doesn’t work like he said. Add or remove item in list can’t trigger Selector(in provider: ^4.0.4 like this), even same Object change attribute.

You must reset watched data to new, for example list filterTitles=[...filterTitles] to trigger Selector when you add or remove item.

There is a optional parameter: shouldRebuild , you set function put conditional expression (return true|false) in it to define which conditional will trigger rebuild.


comments powered by Disqus