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 编程开发」微信公众号 。

相关文章
相关标签/搜索