State management in Flutter is an eternal topic. This article will provide the simplest and most comprehensive introduction to the use of a Provider. Because each point of knowledge may involve an example code (repetitive and simple code), it is longer. I hope it inspires you.

introduce

Provider is a community-built state management tool and a member of Flutter Favorite. Encapsulation based on the InheritedWidget component makes it easier to use. It has a complete set of solutions for the vast majority of situations that developers encounter, and it allows you to develop simple, high-performance, hierarchical applications, so it’s really good!

Because the InheritedWidget is encapsulated around the InheritedWidget component, developers need to know something about it. If you don’t, check out the InheritedWidget in the previous article.

advantage

To quote the official description:

  • Simplified resource allocation and disposal
  • Lazy loading
  • Reduce a lot of template code when creating new classes
  • Support DevTools
  • A more general way of calling InheritedWidget (refer to the Provider. Of/Consumer/Selector)
  • Improved class extensibility, the overall listening architecture time complexity increases exponentially (e.g. ChangeNotifier, O(N))

Note: I also recommend that developers who are new to Flutter use providers.


Providers provide several different types of providers (let’s call them providers) and several usage patterns. In this paper, a variety of modes are explained from two aspects, provider and consumer respectively.

The provider

1, the Provider

The most basic provider consists of receiving an arbitrary value and exposing it, but not updating the UI.

Source:

Provider({
  Key? key,
  required Create<T> create,
  Dispose<T>? dispose,
  bool? lazy,
  TransitionBuilder? builder,
  Widget? child,
})...
Copy the code

Example:

  • Model
class Person{
  String name = "Provider";
}
Copy the code
  • Program entry setup
return Provider<Person>(
  create: (ctx)=> Person(),
  child: const MaterialApp(
    home: ProviderDemo(),
  ),
);
Copy the code

Note: If not specified in all examples, the default state management is placed on top of the top-level MaterialApp, which can be customized by the developer.

  • View
class ProviderDemo extends StatelessWidget { const ProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Consumer<Person>(/// get the Person object anywhere in the program, read the data Builder: (_, Person,child){return Text(person.name); }),),); }}Copy the code

We’ll talk about Consumer in a little bit.

In addition to using create to create a new object model, you can also use an existing object instance. This is a model, not a model in the narrow sense, but a viewModel, a utility class, etc.

Such as:

Provider. Value (value: value, /// value is an existing model object child:...)Copy the code

or

ChangeNotifierProvider. Value (the value: the value, / / / is the value of existing model object child:...).Copy the code

Note: the use of.valueIs to expose the existing object instance, if it is a new model object, be sure to use create

When using the Provider’s create and UPDATE callbacks, the callback function is invoked deferred by default. That is, the create and update functions are called only when the variable is read. Developers can also disable this behavior with the lazy parameter:

Provider(
  create: (_) => Something(),
  lazy: false,
)
Copy the code

We also notice that there is a callback from Dispose:

typedef Dispose<T> = void Function(BuildContext context, T value);

Copy the code

When the Provider is removed, it will Disposer

, and you can release the appropriate resources from this location.

2, ChangeNotifierProvider

It listens for changes in model objects, and when data changes, it also rebuilds the Consumer and updates the UI.

  • Inherit and mix with ChangeNotifier
  • Call thenotifyListeners()

It does not instantiate the model repeatedly, except in individual scenarios. If the instance is no longer called, The ChangeNotifierProvider automatically calls the dispose() method of the model.

  • ChangeNotifier

Listeners can be added to or deleted from listeners, and can also be added to listeners by notifies. Used to notify all observers (in the Provider, or Consumer).

Example:

  • Model
class Person with ChangeNotifier{ String name = "ChangeNotifierProvider"; Void changName({required String newName}){name = newName; notifyListeners(); }}Copy the code
  • Program entry setup
return ChangeNotifierProvider<Person>(
  create: (ctx)=> Person(),
  child: const MaterialApp(
    home: ChangeNotifierProviderDemo(),
  ),
);
Copy the code
  • View
class ChangeNotifierProviderDemo extends StatelessWidget { const ChangeNotifierProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("ChangeNotifierProvider"),), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment spaceEvenly, children: / / / / to get the person object, read the data Consumer < person > (builder: (CTX,person,child) => Text(person.name),), (CTX,person, Child){return ElevatedButton(/// Press button and call method to update onPressed: () => Person.ChangName (newName: "ChangeNotifierProvider updated"), the child: const Text (click "update"));},),),),),); }}Copy the code
  • The results of

Display data; Click the button to modify data successfully.

3, FutureProvider

The FutureProvider has an initial value, receives a Future, and updates dependent components when it enters the complete state.

  • FutureProviderIt will only be rebuilt once
  • Default displays the initial value,FutureWhen the complete state is entered, the UI is updated
  • FutureProviderIt will only be rebuilt once

Example:

  • Model
class Person {
  String? name;
  Person({required this.name});
}
Copy the code
  • Program entry setup
Return FutureProvider<Person>(initialData: Person(name: "initial value "), create: (CTX){return future.delayed (const Duration(seconds:2), () => Person(name: "update FutureProvider")); }, child: const MaterialApp( home: FutureProviderDemo(), ), );Copy the code
  • View
class FutureProviderDemo extends StatelessWidget { const FutureProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("FutureProvider"),), body: Center( child:Consumer<Person>(builder: (ctx,person,child) =>Text(person.name!) ,),),); }}Copy the code
  • The results of

Display the default value first, and update the data after 2s

4, StreamProvider

Listen to the flow and expose the current most recent value, triggering a UI rebuild multiple times.

Example:

  • Model
class Person {
  String? name;
  Person({required this.name});
}
Copy the code
  • Program entry setup
Return StreamProvider<Person>(initialData: Person(name: "initial value "), create: (CTX) {return Stream<Person>. Periodic (const Duration(seconds: 1),(value){ return Person(name: "StreamProvider --- $value"); }); }, child: const MaterialApp( home: StreamProviderDemo(), ), );Copy the code
  • View
class StreamProviderDemo extends StatelessWidget { const StreamProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("FutureProvider"),), body: Center( child: Consumer<Person>(builder: (ctx,person,child) => Text(person.name!) ,),),); }}Copy the code

It is recommended that developers learn about StreamBuilder first.

  • The results of

Display the default values first, update the data every 1s, and refresh the UI.

5, MultiProvder

We use only one Provder, but in practice there will be multiple provders. If we nested them, it would be confusing and unreadable.

Such as:

return ProvderA(
  child: ProvderB(
    child: ProvderC(
      child: ProvderD(
        ...
        child: MaterialApp()
      ) 
    ) 
  )
)
Copy the code

To solve this nested hell, MultiProvder was born. It’s actually a collection of provders, and it just changes the way the code is written.

Example:

  • Model
class Person1 with ChangeNotifier{ String name = "MultiProvider --- 1"; Void changName(){name = "Update MultiProvider -- 1"; notifyListeners(); }}Copy the code
class Person2 with ChangeNotifier{ String name = "MultiProvider --- 2"; Void changName(){name = "Update MultiProvider -- 2"; notifyListeners(); }}Copy the code
  • Program entry setup
return MultiProvider(
  providers: [
    ChangeNotifierProvider<Person1>(
      create: (ctx) => Person1(),
    ),
    ChangeNotifierProvider<Person2>(
      create: (ctx) => Person2(),
    )
  ],
  child: const MaterialApp(
    home: MultiProviderDemo(),
  ),
);
Copy the code

Providers A collection of multiple providers

  • View
class MultiProviderDemo extends StatelessWidget { const MultiProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Consumer<Person1>(builder: (ctx,person1,child) => Text(person1.name)), Consumer<Person2>(builder: (_,person2,child) => Text(person2.name)), Consumer<Person1>( (ctx,person1,child){ return ElevatedButton( onPressed: (){ person1.changName(); }, child: Const Text(" click to modify "),);},),],),); }}Copy the code

6, ProxyProvider

ProxyProvider can be used in daily development when a model is nested with another model, or when the parameters of a model use the values of another model, or when the values of several models are combined into a new model. It can aggregate the values of multiple providers into a new object, passing the result to the provider (provider, not ChangeNotifierProvider), and the new object will update the value synchronously after any provider it depends on is updated.

ProxyProvider<T, R> // R depends on T or uses the value of T, and notifies R when T changesCopy the code

Note: A synchronous update does not mean a synchronous update of the UI, it may just be a value update. Whether or not to update the UI synchronously depends on which dependent provider is used. For example, using the basic provider value has changed (through hot updates or debugging), but the UI is not updated. If the ChangeNotifierProvider is used to update the value, the UI is updated simultaneously.

Example:

  • Model
Class Person extends ChangeNotifier{String name = "name "; Void changName(){name = "update "; notifyListeners(); }}Copy the code

EatModel needs the Person name value to know who is eating:

class EatModel{
  EatModel({required this.name});

  final String name;

  String get whoEat => "$name正在吃饭";
}
Copy the code
  • Program entry setup
return MultiProvider(
  providers: [
    ChangeNotifierProvider<Person>(
      create: (ctx) => Person(),
    ),
    ProxyProvider<Person, EatModel>(
      update: (ctx, person, eatModel) => EatModel(name: person.name),
    )
  ],
  child: const MaterialApp(
    home: ProxyProviderDemo(),
  ),
);
Copy the code
  • View
class ProxyProviderDemo extends StatelessWidget { const ProxyProviderDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Consumer<EatModel>(builder: (CTX,eatModel,child) => Text(eatmodel.whoeat)), Consumer<Person>(// Get the Person object, call method Builder: (CTX,person,child){return ElevatedButton(/// click the button to update the person name, eatModel.whoeat will update onPressed: () = > person. ChangName (), the child: const Text (click "modify"));},),),),),); }}Copy the code
  • The results of

The page shows little tiger teeth eating; After clicking the button: updated for updated little tiger teeth are eating.

ProxyProvider also has different forms: ProxyProvider, ProxyProvider2, ProxyProvider3… ProxyProvider6. The number after the class name is the number of providers that ProxyProvider depends on. It is often difficult to combine a new model with six or more models, and if so, you need to think about whether the engineering architecture is wrong.

7, ChangeNotifierProxyProvider

Unlike similar the ProxyProvider ChangeNotifierProxyProvider its value will be passed to the ChangeNotifierProvider rather than the Provider.

The official example looks a bit complicated, so let’s simplify it by writing an example of an item display and collection list. Of course we should learn from the official example: try to use the private variable _ in Model to reduce the coupling. I’m going to be a little casual here for simplicity.

Example:

  • Model

There are three model objects, namely, ShopModel, ListModel and CollectionListModel.

Are:

class Shop{
  final int id;
  final String name;
  Shop(this.id, this.name);
}
Copy the code
Class ListModel {// Final List <Shop> shops = [Shop(1, "Apple/ Apple 14-inch MacBook"), Shop(2, "HUAWEI/ HUAWEI Mate 40 RS "), Shop(3, "Apple/ Apple 11-inch iPad Pro"), Shop(4, "Xiaomi 12Pro5g Snapdragon 8"), Shop(5, "Apple/ Apple iPhone 13 Pro"), Shop(6, "HUAWEI /HUAWEI Mate X2"), Shop(7," Mi 11 Ultra 5G Phone "), Shop(8, "HUAWEI P40 Pro+ 5G Leica "),]; }Copy the code
Class CollectionListModel extends ChangeNotifier{// Rely onListModel final ListModel _listModel; CollectionListModel(this._listModel); // List<Shop> shops = []; Void add(Shop){shops. Add (Shop); notifyListeners(); } void remove(Shop){shops. Remove (Shop); notifyListeners(); }}Copy the code
  • Program entry setup
return MultiProvider(
  providers: [
    Provider<ListModel>(
      create: (ctx) => ListModel(),
    ),
    ChangeNotifierProxyProvider<ListModel, CollectionListModel>(
      create: (ctx) => CollectionListModel(ListModel()),
      update: (ctx, listModel, collectionModel) => CollectionListModel(listModel),
    )
  ],
  child: MaterialApp(
    theme: ThemeData(
      primarySwatch: Colors.orange,
    ),
    debugShowCheckedModeBanner: false,
    home: const ChangeNotifierProxyProviderDemo(),
  ),
);
Copy the code
  • Widget
class ChangeNotifierProxyProviderDemo extends StatefulWidget { const ChangeNotifierProxyProviderDemo({Key? key}) : super(key: key); @override _ChangeNotifierProxyProviderDemoState createState() => _ChangeNotifierProxyProviderDemoState(); } class _ChangeNotifierProxyProviderDemoState extends State<ChangeNotifierProxyProviderDemo> { int _selectedIndex = 0; final _pages = [const ListPage(), const CollectionPage()]; @override Widget build(BuildContext context) { return Scaffold( body: _pages[_selectedIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _selectedIndex, onTap: (index) { setState(() { _selectedIndex = index; }); }, items: const [ BottomNavigationBarItem( icon: Icon(Icons.list_alt_outlined), label: BottomNavigationBarItem(icon: icon (Icons. Favorite), label: "favorites")],); }}Copy the code

List of products:

class ListPage extends StatelessWidget { const ListPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { ListModel listModel = Provider.of<ListModel>(context); List<Shop> shops = listModel.shops; Return Scaffold(appBar: appBar (title: const Text(" Item list "),), Body: ListView.builder(itemCount: listModel.shops.length, itemBuilder: (ctx,index) => ShopItem(shop: shops[index],), ), ); }}Copy the code

Favorite List:

class CollectionPage extends StatelessWidget { const CollectionPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { CollectionListModel collectionModel = Provider.of<CollectionListModel>(context); List<Shop> shops = collectionModel.shops; Return Scaffold(appBar (title: const Text(" booklist "),), Body: ListView.builder(itemCount: shops.length, itemBuilder: (ctx,index) => ShopItem(shop: shops[index],), ), ); }}Copy the code

Commodity Item:

class ShopItem extends StatelessWidget { const ShopItem({Key? key,required this.shop}) : super(key: key); final Shop shop; @override Widget build(BuildContext context) { return ListTile( leading: CircleAvatar( child: Text("${shop.id}"), ), title: Text(shop.name,style: const TextStyle(fontSize: 17),), trailing: ShopCollectionButton(shop: shop,), ); }}Copy the code

Commodity Collection button:

class ShopCollectionButton extends StatelessWidget { const ShopCollectionButton({Key? key,required this.shop}) : super(key: key); final Shop shop; @override Widget build(BuildContext context) { CollectionListModel collectionModel = Provider.of<CollectionListModel>(context); bool contains = collectionModel.shops.contains(shop); return InkWell( onTap: contains ? ()=> collectionModel.remove(shop) : ()=> collectionModel.add(shop), child: SizedBox( width: 60,height: 60, child: contains ? const Icon(Icons.favorite,color: Colors.redAccent,) : const Icon(Icons.favorite_border), ), ); }}Copy the code

Provider. Of is used to fetch data. The official example uses context.read and context.read, which will be covered later in this article.

  • Results:

8 ListenableProvider.

A special provider for listening objects. The ListenableProvider listens on the object and updates the widgets that depend on it when the listener is invoked. Similar to ChangeNotifierProvider, is its parent. Applies to any Listenable. The ChangeNotifierProvider is normally used, but you can use ListenableProvider if you implement Listenable yourself or use animations.

ListenableProvider provides objects that are subclasses of the Listenable abstract class. The addListener/removeListener method must also be implemented to manually manage listeners. Obviously this is too complicated, so it’s not usually necessary.

class ChangeNotifier implements Listenable
Copy the code

Classes mixed with ChangeNotifier automatically help us manage, so ListenableProvider can also accept classes mixed with ChangeNotifier.

9 ListenableProxyProvider.

ListenableProvider is a variant of ListenableProvider, but works surprisingly well as ChangeNotifierProvider in use. That is, two or more models depend on each other.

I will not write an example here, but I believe developers should be able to use ListenableProvider and ChangeNotifierProvider well.

10, ValueListenableProvider

Listen for ValueListenable and only ValuelistEnable.value is exposed. It is actually dedicated to a ChangeNotifier that only has a single change of data.

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>
Copy the code

It also has two ways: ValueListenableProvider () and ValueListenableProvider value (). The two approaches are almost identical.


consumers

1, the Provider of

We’ve already used this above, remember that in our previous article on InheritedWidget we talked about having a default convention: If the InheritedWidget wants to be exposed, provide a static “of” method to retrieve its object so that the developer can retrieve it directly.

static T of<T>(BuildContext context, {bool listen = true})
Copy the code

Listen: defaults to true to listen for state changes, and false to not listen for state changes.

Provider. Of

(context) is a static method provided by the Provider. When we use this method to obtain a value, the nearest Provider of type T is returned to us, and the whole component tree is not traversed.

2, Consumber

Provider for frequent consumers, check the source code:

Consumer({ Key? key, required this.builder, Widget? child, }) : super(key: key, child: child); . @override Widget buildWithChild(BuildContext context, Widget? child) { return builder( context, Provider.of<T>(context), child, ); }Copy the code

It is found through provider.of

(context). Using provider. of

(context) is much easier than using Consumer. What are the advantages of Consumer?

By contrast, we see that the Consumer has a Widget, right? Child, which is important for greatly narrowing the scope of your control refresh in complex projects.

Let’s use a simple counter example to illustrate:

return Scaffold( body: Consumer( builder: (BuildContext context,CounterModel counterModel,Widget? child){ return Column( children: [ Text("${counterModel.count}"), ElevatedButton( onPressed: ()=> counterModel.increment(), child: Const Text (" click add 1 "),), Text (" other more components "), Text (" other more components "), Text (" other more components "), Text (" other more components "), Text (" other more components "),,); },),);Copy the code

In the example above, many of our later Text components do not use model data and do not need to update their state, but because they are wrapped by Consumer, they are rebuilt every time the data changes! Seriously affecting performance and not elegant!

To solve the above problems, on the one hand, we can adjust the position of the Consumer as much as possible and wrap the Consumer in the components that need to use data. However, there is also a problem that wrapping a large number of consumers alone runs counter to the concept of the birth of the Provider. Is that where we can use the Widget? The child. Let’s optimize for the above example:

return Scaffold( body: Consumer( builder: (BuildContext context,CounterModel counterModel,Widget? child){ return Column( children: [ Text("${counterModel.count}"), ElevatedButton( onPressed: ()=> CounterModel.increment (), child: const Text(" click to increment 1"),), child! ,); }, child: Column( children: Other more components [Text (" "), Text (" other more components "), Text (" other more components "), Text (" other more components "), Text (" other more components "),],),),);Copy the code

Do we use widgets? Child greatly improves performance by wrapping components that do not need to update their state.

3, the Selector

Selector is also a consumer. Similar to Consumer, but with finer control over how build calls Widget methods. A Consumer listens for changes to all data in a Provider, and a Selector listens for changes to one or more values.

For example, the user model Person: has the name, gender, age, height, weight, etc., but we might just update the age, and we don’t want to rebuild the rest of the information, so we can use Selector to do that.

Example:

  • Model
Class Person with ChangeNotifier {String name = "ChangeNotifier "; int age = 18; A double height = 180.0; // Age change void increaseAge() {age ++; notifyListeners(); }}Copy the code
  • Program entry Settings:
return ChangeNotifierProvider(
  create: (ctx) => Person(),
  child: const MaterialApp(
    home: SelectorDemo(),
  ),
);
Copy the code
  • View
class SelectorDemo extends StatelessWidget { const SelectorDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Selector"),), body: Center( child: Selector<Person,int>( selector: (ctx,person) => person.age, builder: (CTX,age,child) {return Column(children: [Text(" age = $age"), child! ,); }, child: Padding( padding: const EdgeInsets.only(top: 50), child: ElevatedButton( onPressed: () => Provider. Of <Person>(context,listen: false).increaseAge(), child: const Text(" click to change the age "),),))); }}Copy the code
  • Results:

Age is 18. Click and age is increased by 1.

Are widgets also used here? Child, like Consumer, greatly Narrows the control’s refresh scope.

4, InheritedContext

InheritedContext actually extends BuildContext within the Provider. There are three ways:

BuildContext.read

Use is similar to provider.of. Used to fetch data, does not trigger a refresh.

Example:

  • Model
Class Person {String name = "Person "; }Copy the code
  • Program entry class Settings:
return Provider(
  create: (ctx) => Person(),
  child: const MaterialApp(
    home: ```
ReadDemo
```(),
  ),
);
Copy the code
  • View
class ReadDemo extends StatelessWidget {
  const ReadDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("ReadDemo"),),
      body: Center(
        child: Text("姓名:${context.read<Person>().name.toString()}"),
      )
    );
  }
}
Copy the code
  • Results:

Display name: small tiger teeth, used in the same way as provider.of ().

BuildContext.watch

As the name suggests, observing, reading and writing dependent data triggers updates. The effect is very similar to that of Consumer and is much simpler to use. The difference is there’s no Consumer Widget? Child’s optimized control refresh function.

Get the Model object with context.watch

(), use and refresh the data.

  • Model
Class Person with ChangeNotifier{String name = "ChangeNotifier "; ChangeName (){name = "updatename "; notifyListeners(); }}Copy the code
  • Program entry class Settings:
return ChangeNotifierProvider(
  create: (ctx) => Person(),
  child: const MaterialApp(
    home: WatchDemo(),
  ),
);
Copy the code
  • View
class WatchDemo extends StatelessWidget { const WatchDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) {final person = context.watch< person >(); return Scaffold( appBar: AppBar(title: const Text("Watch"),), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment spaceEvenly, children: [Text (" name: ${person. Name} "), the Padding Padding: const EdgeInsets. Only (top: 50), child: ElevatedButton(onPressed: () => Person.changename (), child: const Text(" Pressed "),),],))); }}Copy the code
  • Results:

Display: name: small tiger tooth, click the button: display update to name: refresh small tiger tooth.

BuildContext.select

Similar to the Selector mentioned earlier, specifying some properties of the listener is simpler to use. The difference is that there’s no Selector Widget, right? Child’s optimized control refresh function.

  • Model
Class Person with ChangeNotifier {String name = "ChangeNotifier "; int age = 18; A double height = 180.0; void increaseAge() { age ++; notifyListeners(); }}Copy the code
  • Program entry class:
return ChangeNotifierProvider(
  create: (ctx) => Person(),
  child: const MaterialApp(
    home: SelectDemo(),
  ),
);
Copy the code
  • View
class SelectDemo extends StatelessWidget { const SelectDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) {final age = context.select((Person Person) => person.age); return Scaffold( appBar: AppBar(title: const Text("Watch"),), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment spaceEvenly, children: [Text (" age: $age "), the Padding Padding: const EdgeInsets. Only (top: 50), the child: ElevatedButton( onPressed: () => Provider.of<Person>(context, listen: false)..increaseAge(), child: Const Text(" click to change name "),),),],))); }}Copy the code
  • Results:

Age is 18. Click and age is increased by 1.


These are the common ways to use providers. Developers can use them according to their own needs.