Flutter 全局狀態管理之 Provider 初探

1、什麼是全局狀態管理

當咱們在使用 Flutter 進行應用開發時,可能須要不一樣的頁面共享應用或者說變量的狀態,當這個狀態發生改變時,全部依賴這個狀態的 ui 都會隨之發生改變。在同一個頁面中還好說,直接經過 setState 就能夠達到目的,要是不一樣的頁面呢,或者當應用變得很是複雜,頁面很是多的時候,這個時候全局狀態管理就顯得很是重要了。html

在 Flutter 中,狀態管理能夠有以下幾種方式:react

一、setState

flutter 中最簡單使 ui 根據狀態發生改變的方式。git

二、 InheritedWidget & InheritedModel

InheritedWidget 和 InheritedModel 是 flutter 原生提供的狀態管理解決方案。 當InheritedWidget發生變化時,它的子樹中全部依賴了它的數據的Widget都會進行rebuild,這使得開發者省去了維護數據同步邏輯的麻煩。github

三、Provider & Scoped Model

Provider 與 Scoped Model 都屬於第三方庫,二者使用起來差很少,其中 Provider 是 Google I/O 2019 大會上官方推薦的狀態管理方式。編程

四、Redux

在 Redux 狀態管理中,全部的狀態都儲存在Store裏,Flutter 中的 Widget 會根據這個 Store 去渲染視圖,而狀態的改變也是經過 Reduex 裏面的 action 來進行的。數組

五、BLoC / Rx

BLoC的全稱是 業務邏輯組件(Business Logic Component)。就是用reactive programming方式構建應用,一個由流構成的徹底異步的世界。 BLoc 能夠看做是 Flutter 中的異步事件總線,固然在除了 BLoc 外,Flutter 中有專門的響應式編程庫,就是RxDart,RxDart是基於ReactiveX標準API的Dart版本實現,由Dart標準庫中Stream擴展而成。bash

2、Provider 介紹和使用

一、Provider 是什麼

Provider 是 Google 官方推薦的狀態管理解決方案,本質上也是使用 InheritedWidget 來進行狀態管理的,因此也能夠理解爲 Provider 是 Flutter 中的語法糖,主要是對 InheritedWidget 的封裝方便咱們的使用。微信

Provider 使用起來很是方便,訪問數據的方式有兩種,不管是獲取狀態仍是更新狀態,都是這兩種:app

  • Provider.of(context)
  • Consumer
二、Provider 基本使用

接下來直接參考官方的 Demo 了。less

一、要添加依賴:

provider: ^3.1.0
複製代碼

二、定義數據 model

class Counter with ChangeNotifier {
  ///這個 model 只管理一個變量。
  int value = 0;

  ///操做變量
  void increment() {
    value += 1;
    notifyListeners();
  }
}
複製代碼

三、使用 ChangeNotifierProvider 進行數據管理

ChangeNotifierProvider(
      // Initialize the model in the builder. That way, Provider
      // can own Counter's lifecycle, making sure to call `dispose` // when not needed anymore. ///builder 會指定數據 model 並初始化。 builder: (context) => Counter(), child: MyApp(), ), 複製代碼

四、監聽狀態改變可使用 Provider.of 或者 Consumer

Consumer<Counter>(
              builder: (context, counter, child) => Text(
                '${counter.value}',
                style: Theme.of(context).textTheme.display1,
              ),
            ),

            Text('使用 Provider.of 方式 獲取 model:'),
            Text('${_counter.value}',),
複製代碼

五、改變數據,一樣可使用 Provider.of 或者 Consumer

floatingActionButton: FloatingActionButton(
        /// listen 爲 false 表示不監聽狀態改變,默認時 true
        onPressed: () => Provider.of<Counter>(context, listen: false).increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),


      ///須要修改 Model 一樣可使用 Consumer 的方式
//        floatingActionButton: Consumer<Counter>(
//          builder: (context, Counter counter, child) => FloatingActionButton(
//            onPressed: counter.increment,
//            child: child,
//          ),
//          child: Icon(Icons.add),
//        ),


複製代碼

完整代碼:

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

void main() {
  runApp(
    ///使用 ChangeNotifierProvider ,這個 Provider 將數據 model 粘合在一塊兒,數據改變時,保證 MyApp 或者其子 Widget ui 更新。
    ChangeNotifierProvider(
      // Initialize the model in the builder. That way, Provider
      // can own Counter's lifecycle, making sure to call `dispose` // when not needed anymore. ///builder 會指定數據 model 並初始化。 builder: (context) => Counter(), child: MyApp(), ), ); } /// Simplest possible model, with just one field. /// /// [ChangeNotifier] is a class in `flutter:foundation`. [Counter] does /// _not_ depend on Provider. /// /// class Counter with ChangeNotifier { ///這個 model 只管理一個變量。 int value = 0; ///操做變量 void increment() { value += 1; notifyListeners(); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { ///經過 Provider.of 方式獲取 model final _counter = Provider.of<Counter>(context); return Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('使用 Consumer 獲取 model:'), // Consumer looks for an ancestor Provider widget // and retrieves its model (Counter, in this case). // Then it uses that model to build widgets, and will trigger // rebuilds if the model is updated. ///Consumer 迴向上尋找 Provider 類型的父類 Widget,而且取出 Provider 關聯的 Model,根據這個 model 來構建 widget ///而且當 model 數據發生改變時,回觸發更新。 /// Consumer<Counter>( builder: (context, counter, child) => Text( '${counter.value}', style: Theme.of(context).textTheme.display1, ), ), Text('使用 Provider.of 方式 獲取 model:'), Text('${_counter.value}',), ], ), ), floatingActionButton: FloatingActionButton( /// listen 爲 false 表示不監聽狀態改變,默認時 true onPressed: () => Provider.of<Counter>(context, listen: false).increment(), tooltip: 'Increment', child: Icon(Icons.add), ), ///須要修改 Model 一樣可使用 Consumer 的方式 // floatingActionButton: Consumer<Counter>( // builder: (context, Counter counter, child) => FloatingActionButton( // onPressed: counter.increment, // child: child, // ), // child: Icon(Icons.add), // ), ); } } 複製代碼

效果:

三、多個頁面數據共享

接下來看一下如何在不一樣的頁面中共享數據作到全局狀態管理。

仍是以官方 Demo 爲例說明。 假設有一個購物應用程序,有一個購物車頁面和一個結帳頁面,購物車頁面添加商品,結帳頁面能夠看到全部添加的商品,對商品來講,就是共享的數據源。 先看一下效果:

一、定義數據 model

在這個示例中,有兩個 model,一個是商品模型,一個是購物車,購物車存放選擇的商品。

  • 商品 Model
/// A proxy of the catalog of items the user can buy.
///
/// In a real app, this might be backed by a backend and cached on device.
/// In this sample app, the catalog is procedurally generated and infinite.
///
/// For simplicity, the catalog is expected to be immutable (no products are
/// expected to be added, removed or changed during the execution of the app).
class CatalogModel {
  static const _itemNames = [
    'Code Smell',
    'Control Flow',
    'Interpreter',
    'Recursion',
    'Sprint',
    'Heisenbug',
    'Spaghetti',
    'Hydra Code',
    'Off-By-One',
    'Scope',
    'Callback',
    'Closure',
    'Automata',
    'Bit Shift',
    'Currying',
  ];

  /// Get item by [id].
  ///
  /// In this sample, the catalog is infinite, looping over [_itemNames].
  Item getById(int id) => Item(id, _itemNames[id % _itemNames.length]);

  /// Get item by its position in the catalog.
  Item getByPosition(int position) {
    // In this simplified case, an item's position in the catalog // is also its id. return getById(position); } } @immutable class Item { final int id; final String name; final Color color; final int price = 42; Item(this.id, this.name) // To make the sample app look nicer, each item is given one of the // Material Design primary colors. : color = Colors.primaries[id % Colors.primaries.length]; @override int get hashCode => id; @override bool operator ==(Object other) => other is Item && other.id == id; } 複製代碼

只是簡單的模擬一下用戶選擇的商品,包含 id,name,color,price 這四個字段。

  • 購物車 Model
class CartModel extends ChangeNotifier {
  /// The current catalog. Used to construct items from numeric ids.
  final CatalogModel _catalog;

  /// 購物車中存放商品的 list,只存 id 就行
  final List<int> _itemIds;

  /// Construct a CartModel instance that is backed by a [CatalogModel] and
  /// an optional previous state of the cart.
  ///
  /// If [previous] is not `null`, it's items are copied to the newly /// constructed instance. CartModel(this._catalog, CartModel previous) : assert(_catalog != null), _itemIds = previous?._itemIds ?? []; /// 將存放商品 id 的數組轉換爲存放商品的數值,函數式編程。 List<Item> get items => _itemIds.map((id) => _catalog.getById(id)).toList(); /// 獲取價格總和,dart 中的 List 中有兩個累加的方法 reduce 和 fold,fold 能夠提供一個初始值。 int get totalPrice => items.fold(0, (total, current) => total + current.price); ///添加商品,這個方法時外界能夠修改 list 的惟一途徑 void add(Item item) { _itemIds.add(item.id); // This line tells [Model] that it should rebuild the widgets that // depend on it. notifyListeners(); } } 複製代碼

商品中經過 List 存放選擇的商品。這裏的購物車 Model 實現的是 ChangeNotifier,作爲可改變的數據源。對於不一樣類型的可改變數據源,Provider 提供了不一樣的類提供咱們選擇,常見的有以下幾種:

二、購物車頁面提供商品選擇,改變數據狀態

class MyCatalog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          _MyAppBar(),
          ///上間距
          SliverToBoxAdapter(child: SizedBox(height: 12)),
          SliverList(
            delegate: SliverChildBuilderDelegate(
                (context, index) => _MyListItem(index)
              ,
                childCount: 25      ///原本時無限加載的,這裏加上數量限制。
            ),
          ),
        ],
      ),
    );
  }
}

class _AddButton extends StatelessWidget {
  final Item item;

  const _AddButton({Key key, @required this.item}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    ///經過 Provider.of 方式使用 CartModel
    var cart = Provider.of<CartModel>(context);

    return FlatButton(
      ///判斷是否爲空,不爲空 list 中添加 item
      onPressed: cart.items.contains(item) ? null : () => cart.add(item),
      splashColor: Theme.of(context).primaryColor,
      child: cart.items.contains(item)
          ? Icon(Icons.check, semanticLabel: 'ADDED')
          : Text('ADD'),
    );
  }
}

class _MyAppBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      title: Text('Catalog', style: Theme.of(context).textTheme.display4),
      floating: true,
      actions: [
        IconButton(
          icon: Icon(Icons.shopping_cart),
          onPressed: () => Navigator.pushNamed(context, '/cart'),
        ),
      ],
    );
  }
}

class _MyListItem extends StatelessWidget {
  final int index;

  _MyListItem(this.index, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    ///Provider.of 方式獲取 model
    var catalog = Provider.of<CatalogModel>(context);
    var item = catalog.getByPosition(index);
    var textTheme = Theme.of(context).textTheme.title;

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: LimitedBox(
        maxHeight: 48,
        child: Row(
          children: [
            AspectRatio(
              aspectRatio: 1,
              child: Container(
                color: item.color,
              ),
            ),
            SizedBox(width: 24),
            Expanded(
              child: Text(item.name, style: textTheme),
            ),
            SizedBox(width: 24),
            _AddButton(item: item),
          ],
        ),
      ),
    );
  }
}

複製代碼

三、購物車頁面,獲取數據

class MyCart extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Cart', style: Theme.of(context).textTheme.display4),
        backgroundColor: Colors.white,
      ),
      body: Container(
        color: Colors.yellow,
        child: Column(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(32),
                child: _CartList(),
              ),
            ),
            Divider(height: 4, color: Colors.black),
            ///價格
            _CartTotal()
          ],
        ),
      ),
    );
  }
}

class _CartList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var itemNameStyle = Theme.of(context).textTheme.title;

    ///使用 Provider.of 方式獲取 CartModel
    var cart = Provider.of<CartModel>(context);

    return ListView.builder(
      itemCount: cart.items.length,
      itemBuilder: (context, index) => ListTile(
        leading: Icon(Icons.done),
        title: Text(
          cart.items[index].name,
          style: itemNameStyle,
        ),
      ),
    );
  }
}

class _CartTotal extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var hugeStyle = Theme.of(context).textTheme.display4.copyWith(fontSize: 48);

    return SizedBox(
      height: 200,
      child: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ///使用 Consumer 方式使用 CartModel
            Consumer<CartModel>(
                builder: (context, cart, child) =>
                    Text('\$${cart.totalPrice}', style: hugeStyle)),
            SizedBox(width: 24),
            FlatButton(
              onPressed: () {
                Scaffold.of(context).showSnackBar(
                    SnackBar(content: Text('Buying not supported yet.')));
              },
              color: Colors.white,
              child: Text('BUY'),
            ),
          ],
        ),
      ),
    );
  }
}

複製代碼

github

參考:

https://medium.com/flutter-community/flutter-statemanagement-with-provider-ee251bbc5ac1
https://flutter.cn/docs/development/data-and-backend/state-mgmt/simple
https://pub.dev/packages/provider#-readme-tab-
https://pub.dev/documentation/provider/latest/provider/provider-library.html
複製代碼

最後

歡迎關注「Flutter 編程開發」微信公衆號 。

相關文章
相關標籤/搜索