One, foreword

Provider is one of the status management methods recommended by Google. You are advised to check the Github address of Provider to learn the basic usage.

Most of the articles on the web that introduce providers are about ChangeNotifierProvider, and after reading it you really know what it does and how to use it.

However, there are other providers for us to use, so what are the differences and connections between them? There is no detailed Demo of their use in official documentation, so this article will summarize their usage and differences.

Providers are classified as follows:

  • Provider
  • ListenableProvider
  • ChangeNotifierProvider
  • ValueListenableProvider
  • StreamProvider
  • FutureProvider

Second, the Provider

2.1 Constructors

The constructor of the Provider is as follows:

  Provider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
Copy the code
  • builder:T Function(BuildContext context)Returns the data to be sharedModel.
  • dispose:void Function(BuildContext context, T value)To release resources in a callback.

2.2 Advantages & Disadvantages

Advantages of Providers:

  • Data sharing: YesProvider.of<T>(context)Method, can be in toProviderIs obtained from the subtree of the root nodeTThe object.
  • throughdisposeParameter to release resources.

However, providers have an obvious drawback:

  • Its listeners cannot be notified when shared data changes.

2.3 Counter Examples

Let’s use a classic counter Demo to demonstrate the use of the Provider. For comparison purposes, we’ll use the same example when we introduce other providers.

According to the two features of Provider, we can use it to obtain sink in BLoc and finally release resources.

  • First let’s define a simple oneBlocModel.
import 'dart:async';

class ProviderBloc {

  int _count = 0;
  var _countController = StreamController<int>.broadcast();

  Stream<int> get stream => _countController.stream;
  int getcount => _count; increment() { _countController.sink.add(++_count); } dispose() { _countController.close(); }}Copy the code
  • Next, write the main interface.
    • throughProvider.of<ProviderBloc>(context)We can get it anywhereProviderBlocObject, getsinkTo change the data.
    • useStreamBuilderListening to theStreamIn the data changes, refresh the interface.
    • In the originalBlocOn the model, the top levelProviderprovidesdisposeCallback for resource release.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_bloc.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Provider<ProviderBloc>(
      builder: (context) => ProviderBloc(),
      dispose: (context, bloc) => bloc.dispose(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Provider Demo')), body: CounterLabel(), floatingActionButton: CounterButton(), ), ), ); }}class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<ProviderBloc>(context).increment,
      child: constIcon(Icons.add), ); }}class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: StreamBuilder<int>(
        builder: (context, snapshot) {
          return Text('you have push ${snapshot.data} times');
        },
        initialData: 0, stream: Provider.of<ProviderBloc>(context).stream, ), ); }}Copy the code

Third, ChangeNotifierProvider

ChangeNotifierProvider should be the most common, most of the introduction of Provider articles are based on it as an example, compared with the Provider, its biggest advantage is to solve the problem of not listening after data changes.

3.1 Constructors

The ChangeNotifierProvider constructor is as follows:

  ChangeNotifierProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Widget child,
  })
Copy the code
  • builder:T Function(BuildContext context)Returns the data to be sharedModel.

3.2 ChangeNotifier

When using ChangeNotifierProvider, it requires that the data Model returned from Builder be a subclass of ChangeNotifier.

  • After changing the data, callnotifyListener()Methods.
  • By rewriting itdisposeMethod, can complete the sumProviderSame resource release work.
class Counter with ChangeNotifier {

  int _count = 0;
  int get count => _count;

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

  @override
  void dispose() {
    super.dispose(); }}Copy the code

3.3 the Provider of (context) & Consumer

In Provider articles, both provider.of

(context) and Consumer are compared, and Consumer is generally recommended because it reconstructs the listener’s Widget more narrowly after data changes.

The use of providers in a project can be divided into two roles, the trigger of data changes and the listener:

  • Trigger: If you just need to get the datamodel, does not need to listen for changes (such as button clicking), recommendedProvider.of<Counter>(context, listen: false)To get the datamodel.
  • Listener: RecommendedConsumer.

3.4 example

3.4.1 Defining the data model

  • First, define the data model:
    • After changing the data, callnotifyListenersMethods.
    • If resources need to be freed, they need to be overriddendisposeMethods.
import 'package:flutter/foundation.dart';

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

  void increment() {
    _count++;
    notifyListeners();
  }
  
  @override
  void dispose() {
    super.dispose(); }}Copy the code

3.4.2 master file

import 'counter_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Counter>(
      builder: (context) => Counter(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Provider Demo')), body: CounterLabel(), floatingActionButton: CounterButton(), ), ) ); }}class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<Counter>(context, listen : false).increment,
      child: constIcon(Icons.add), ); }}class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<Counter>(
          builder: (BuildContext context, Counter counter, Widget child) {
            return Text('you have push ${counter.count} times'); })); }}Copy the code

Four, ListenableProvider

4.1 Constructors

The ListenableProvider constructor is:

  ListenableProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
Copy the code
  • builder:T Function(BuildContext context)Returns the data to be sharedModel.
  • dispose:void Function(BuildContext context, T value)To release resources in a callback.

4.2 Comparison with ChangeNotifierProvider

ChangeNotifierProvider ChangeNotifierProvider

class ChangeNotifierProvider<T extends ChangeNotifier>
    extends ListenableProvider<T> implements SingleChildCloneableWidget {
  
  static void_disposer(BuildContext context, ChangeNotifier notifier) => notifier? .dispose(); ChangeNotifierProvider({ Key key,@required ValueBuilder<T> builder,
    Widget child,
  }) : super(key: key, builder: builder, dispose: _disposer, child: child);

}
Copy the code

It can be seen from the source code, ListenableProvider and ChangeNotifierProvider is actually the father and son of the relationship, ChangeNotifierProvider on its basis:

  • Limited datamodelThe upper limit, the requirement must beChangeNotifierThe subclass.
  • Through rewritingChangeNotifier.dispose()To complete the resource release without passing indisposeParameters toChangeNotifierProvider.

4.3 example

When using ListenableProvider, if we do not define the data model as a subclass of ChangeNotifier, then we need to manage listeners ourselves. For convenience, we will continue to use ChangeNotifier. The use of the ChangeNotifierProvider is the same elsewhere.

import 'counter_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListenableProvider<Counter>(
        builder: (context) => Counter(),
        dispose: (context, counter) => counter.dispose(),
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')), body: CounterLabel(), floatingActionButton: CounterButton(), ), ) ); }}class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<Counter>(context, listen: false).increment,
      child: constIcon(Icons.add), ); }}class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<Counter>(
          builder: (BuildContext context, Counter counter, Widget child) {
            return Text('you have push ${counter.count} times'); })); }}Copy the code

Fifth, ValueListenableProvider

5.1 Constructors

  ValueListenableProvider({
    Key key,
    @required ValueBuilder<ValueNotifier<T>> builder,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
Copy the code
  • builder:T Function(BuildContext context)Returns the data to be sharedModel.
  • dispose:void Function(BuildContext context, T value)To release resources in a callback.

5.2 ValueNotifier

ValueListenableProvider requires that the object returned by Builder must be a subclass of ValueNotifier

, where T is the data type to be shared.

ValueNotifier is defined as follows:

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {

  ValueNotifier(this._value);

  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}
Copy the code

ValueNotifier is a subclass of ChangeNotifier. When changing _value, notifyListeners are automatically called.

5.3 example

5.3.1 Defining subclasses of ValueNotifier

import 'package:flutter/foundation.dart';

class CounterModel {

  int count;
  CounterNotifier wrapper;

  CounterModel(this.count);

}

class CounterNotifier extends ValueNotifier<CounterModel> {

  CounterNotifier(CounterModel value) : super(value) {
    value.wrapper = this;
  }

  @override
  void dispose() {
    super.dispose(); }}Copy the code

5.3.2 master file

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_notifier.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ValueListenableProvider<CounterModel>(
        builder: (context) => CounterNotifier(CounterModel(0)),
        updateShouldNotify: (model1, model2) {
          print('updateShouldNotify');
          returnmodel1.count ! = model2.count; }, child: MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Provider Demo')), body: _CounterLabel(), floatingActionButton: _CounterButton(), ), ) ); }}class _CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        CounterModel oldModel = Provider.of<CounterModel>(context, listen: false);
        CounterModel newModel = CounterModel(oldModel.count + 1);
        newModel.notifier = oldModel.notifier;
        oldModel.notifier.value = newModel;
        return;
      },
      child: constIcon(Icons.add), ); }}class _CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<CounterModel>(
          builder: (BuildContext context, CounterModel model, Widget child) {
            return Text('you have push ${model.count} times'); })); }}Copy the code

5.4 question

Notice how notifyListeners() are triggered by changing _value when using ValueNotifier

, whereas provider. of

(context, listen: _value is false) to get the object, so you also need to save ValueNotifier within it < T > references (or defined ValueNotifier into a singleton pattern), to set up a triggered effect, feel very strange to use, the use of don’t know if I have a question, There are no examples online.

Six, StreamProvider

StreamProvider is used in conjunction with Bloc.

6.1 Constructors

6.1.1 Using the Stream construct

  StreamProvider({
    Key key,
    @required ValueBuilder<Stream<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
Copy the code
  • builderReturns theBlocIn theStream.
  • initialData: Initial data.
  • catchError: Callback when an error occurs.

6.1.2 Using the StreamController construct

  StreamProvider.controller({
    Key key,
    @required ValueBuilder<StreamController<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
Copy the code
  • builderReturns theBlocIn theStreamController.

6.2 example

6.2.1 Defining the Singleton Mode

import 'dart:async';

class ProviderBloc {

  static ProviderBloc _instance;

  ProviderBloc._internal() {
    print("_internal");
  }

  static ProviderBloc _getInstance() {
    if (_instance == null) {
      _instance = ProviderBloc._internal();
    }
    return _instance;
  }

  factory ProviderBloc() => _getInstance();
  static ProviderBloc get instance => _getInstance();

  int _count = 0;
  var _countController = StreamController<int>.broadcast();

  Stream<int> get stream => _countController.stream;
  int getcount => _count; increment() { _countController.sink.add(++_count); } dispose() { _countController.close(); }}Copy the code

6.2.2 master file

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_bloc.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return StreamProvider<int> (
        builder: (context) {
          return ProviderBloc().stream;
        },
        catchError: (BuildContext context, Object error) {},
        initialData: 0,
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')), body: CounterLabel(), floatingActionButton: CounterButton(), ), ) ); }}class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: ProviderBloc().increment,
      child: constIcon(Icons.add), ); }}class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<int>(
          builder: (BuildContext context, int value, Widget child) {
            return Text('you have push $value times'); })); }}Copy the code

Seven, FutureProvider

7.1 Constructors

  FutureProvider({
    Key key,
    @required ValueBuilder<Future<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
Copy the code
  • builder: Returns aFuture<T>Object, which is emitted when the result is returned by an asynchronous taskConsumerThe reconstruction.
  • initialData: Initial data.
  • catchError: An error callback occurred.

7.2 And FutureBuilder differences

Unlike the Provider scenario we discussed earlier, FutureProvider is similar to FutureBuilder in that it loads a component before data is returned, waits for the data to return a value, and then redraws to return another component.

The differences between it and FutureBuilder are:

  • FutureBuilderThe data request and presentation are all in one component, whileFutureProviderIs the requester of the data,ConsumerIt’s the showman.
  • FutureBuilderThe requester and presenter are in a one-to-one relationship, whileFutureProviderIt could be a one-to-many relationship.

7.3 example

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FutureProvider<int>(
        builder: (context) => _request(),
        initialData: 0,
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')),
            body: CounterLabel(),
          ),
        )
    );
  }

  Future<int> _request() async {
    return await Future<int>.delayed(Duration(milliseconds: 3000)).then((int value) {
        return 300; }); }}class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(children: <Widget>[
        Consumer<int>(
            builder: (BuildContext context, int count, Widget child) {
              return Text('Observer1=$count');
            }),
        Consumer<int>(
            builder: (BuildContext context, int count, Widget child) {
              return Text('Observer2=$count'); }),])); }}Copy the code

Seven, summary

Compare the usage modes of the preceding providers: ChangeNotifierProvider and StreamProvider are more suitable for our daily use scenarios.

And the other three:

  • Provider: Cannot monitor data changes, directly out.
  • ListenableProvider:ChangeNotifierProviderThe original version of.
  • ValueListenableProvider:ChangeNotifierProvider“, but it feels even more troublesome to use.