Flutter 知識梳理 (狀態管理) - Provider 之各類 XXProvider 的使用姿式

1、前言

Provider是目前Google推薦的狀態管理方式之一,建議你們能夠先看一下 Provider 的 Github 地址 瞭解基本的用法。git

網上大多數介紹Provider的文章講的都是ChangeNotifierProvider,看完以後確實知道它是幹什麼的,以及怎麼用。github

然而其實還有其它的Provider供咱們使用,那麼它們之間的區別和聯繫是什麼呢,官方文檔對它們的使用也沒有詳細的Demo,這篇文章就來總結一下它們的用法和區別。app

Provider的分類有以下幾種:less

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

2、Provider

2.1 構造函數

Provider的構造函數以下:異步

Provider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
複製代碼
  • builderT Function(BuildContext context),返回要共享的數據Model
  • disposevoid Function(BuildContext context, T value),在回調中釋放資源。

2.2 優勢 & 缺點

Provider的優勢:async

  • 數據共享:經過Provider.of<T>(context)方法,能夠在以Provider爲根節點的子樹中獲取到T的對象。
  • 經過dispose參數,能夠進行資源的釋放。

可是Provider也有一個明顯的缺點:ide

  • 在共享數據發生改變時,不能通知它的監聽者

2.3 計數器例子

下面咱們用一個經典的計數器Demo演示一下Provider的使用,爲了方便對比,後面在介紹其它Provider時,也使用該例子。函數

根據Provider的兩個特色,咱們能夠用它來實現BLocsink的獲取,以及最後資源的釋放。ui

  • 首先咱們定義一個簡單的Bloc模型。
import 'dart:async';

class ProviderBloc {

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

  Stream<int> get stream => _countController.stream;
  int get count => _count;

  increment() {
    _countController.sink.add(++_count);
  }

  dispose() {
    _countController.close();
  }
}
複製代碼
  • 接下來編寫主界面。
    • 經過Provider.of<ProviderBloc>(context)咱們能夠在任意的地方獲取到ProviderBloc對象,得到sink來更改數據。
    • 使用StreamBuilder監聽Stream中數據的變化,刷新界面。
    • 在原始的Bloc模型上,頂層的Provider提供了dispose回調,用於資源的釋放。
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: const Icon(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,
      ),
    );
  }
}
複製代碼

3、ChangeNotifierProvider

ChangeNotifierProvider應該是你們見的最多的,大多數介紹Provider的文章都是以它爲例子,和 Provider 相比,它最大的優勢就是解決了數據改變後沒法監聽的問題this

3.1 構造函數

ChangeNotifierProvider的構造函數爲以下:

ChangeNotifierProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Widget child,
  })
複製代碼
  • builderT Function(BuildContext context),返回要共享的數據Model

3.2 ChangeNotifier

使用ChangeNotifierProvider時,它要求builder返回的數據Model必須是ChangeNotifier的子類。

  • 在改變數據後,調用notifyListener()方法。
  • 經過重寫它的dispose方法,能夠完成和Provider同樣的資源釋放工做。
class Counter with ChangeNotifier {

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

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

  @override
  void dispose() {
    super.dispose();
  }
}
複製代碼

3.3 Provider.of(context) & Consumer

在介紹Provider的文章中,Provider.of<T>(context)Consumer都會被拿來對比,通常都會推薦使用Consumer由於它會將數據發生變化後,把監聽者的 Widget 重建的範圍限制地更小

項目中Provider的使用,能夠分爲兩個角色,數據改變的 觸發者監聽者

  • 觸發者:若是隻是須要獲取到數據model,不須要監聽變化(例如點擊按鈕),推薦使用Provider.of<Counter>(context, listen: false)來獲取數據model
  • 監聽者:推薦使用Consumer

3.4 例子

3.4.1 定義數據模型

  • 首先,定義數據模型:
    • 在改變數據後,調用notifyListeners方法。
    • 若是須要釋放資源,那麼須要重寫dispose方法。
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();
  }
}
複製代碼

3.4.2 主文件

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: const Icon(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');
          }),
    );
  }
}
複製代碼

4、ListenableProvider

4.1 構造函數

ListenableProvider的構造函數爲:

ListenableProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
複製代碼
  • builderT Function(BuildContext context),返回要共享的數據Model
  • disposevoid Function(BuildContext context, T value),在回調中釋放資源。

4.2 和 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);

}
複製代碼

從源碼上能夠看出,ListenableProviderChangeNotifierProvider實際上是 父與子的關係ChangeNotifierProvider在它的基礎上:

  • 限制數據model的上限,要求必須是ChangeNotifier的子類。
  • 經過重寫ChangeNotifier.dispose()來完成資源的釋放,不須要傳入dispose參數給ChangeNotifierProvider

4.3 例子

使用ListenableProvider時,假如咱們沒有將數據模型定義成ChangeNotifier的子類,那麼須要本身進行監聽者的管理,爲了方便,咱們仍是繼續使用ChangeNotifier,其它地方的使用和ChangeNotifierProvider都是同樣的。

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: const Icon(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');
          }),
    );
  }
}
複製代碼

5、ValueListenableProvider

5.1 構造函數

ValueListenableProvider({
    Key key,
    @required ValueBuilder<ValueNotifier<T>> builder,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
複製代碼
  • builderT Function(BuildContext context),返回要共享的數據Model
  • disposevoid Function(BuildContext context, T value),在回調中釋放資源。

5.2 ValueNotifier

ValueListenableProvider要求builder返回的對象必須是ValueNotifier<T>的子類,T是須要共享的數據類型。

ValueNotifier的定義以下:

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)';
}
複製代碼

ValueNotifier是咱們前面屢次提到的ChangeNotifier的子類,在改變_value時,自動調用了notifyListeners方法,那麼就參照以前的方法來使用它。

5.3 例子

5.3.1 定義 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();
  }

}
複製代碼

5.3.2 主文件

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');
          return model1.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: const Icon(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');
          }),
    );
  }
}
複製代碼

5.4 疑問

這裏有一個問題困擾了我好久:就是使用ValueNotifier<T>時必需要改變_value纔會觸發notifyListeners()方法,而經過Provider.of<T>(context, listen: false)拿到的對象是_value,所以還須要在它裏面保存ValueNotifier<T>的引用(或者將ValueNotifier定義成單例的模式),再設置一次達到觸發的效果,感受用起來很奇怪,不知道是否是個人使用方式有問題,網上也沒有找到相關的例子。

6、StreamProvider

StreamProvider用來結合Bloc使用。

6.1 構造函數

6.1.1 使用 Stream 構造

StreamProvider({
    Key key,
    @required ValueBuilder<Stream<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
複製代碼
  • builder:返回Bloc中的Stream
  • initialData:初始數據。
  • catchError:發生錯誤時候的回調。

6.1.2 使用 StreamController 構造

StreamProvider.controller({
    Key key,
    @required ValueBuilder<StreamController<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
複製代碼
  • builder:返回Bloc中的StreamController

6.2 例子

6.2.1 定義單例模式

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 get count => _count;

  increment() {
    _countController.sink.add(++_count);
  }

  dispose() {
    _countController.close();
  }

}
複製代碼

6.2.2 主文件

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: const Icon(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');
          }),
    );
  }
}
複製代碼

7、FutureProvider

7.1 構造函數

FutureProvider({
    Key key,
    @required ValueBuilder<Future<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
複製代碼
  • builder:返回一個Future<T>對象,異步任務的返回結果,在結果返回後,會觸發Consumer的重建。
  • initialData:初始數據。
  • catchError:發生錯誤的回調。

7.2 和 FutureBuilder 的區別

FutureProvider和咱們以前討論的Provider場景不太同樣,它和FutureBuilder比較相似,就是在數據返回以前加載一個組件,等待數據返回值後,重繪返回另外一個組件。

它和FutureBuilder的區別在於:

  • FutureBuilder的數據請求和展現都是在一個組件當中,而FutureProvider是數據的請求者,Consumer是展現者。
  • FutureBuilder的請求者和展現者是一對一的關係,而FutureProvider能夠是一對多的關係。

7.3 例子

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');
            }),
      ],)
    );
  }
}
複製代碼

7、小結

對比以上幾種Provider的使用方式:仍是ChangeNotifierProviderStreamProvider比較符合咱們平時的使用場景。

而其它三種:

  • Provider:不能監聽數據改變,直接淘汰。
  • ListenableProviderChangeNotifierProvider的原始版本。
  • ValueListenableProviderChangeNotifierProvider的加強版本,可是怎麼感受用起來還更麻煩了。
相關文章
相關標籤/搜索