Flutter开始干系列-状态管理Provider3

4,759 阅读3分钟

直接开始干,没有为什么~


上一篇看了官方的默认的计数 Demo ,在 2019 Google I/O 大会中, Provider 代替 Provide 成为官方推荐的状态管理方式之一。

Provider 可以干什么?引用官方的一段话:

By using widgets for state management, provider can guarantee:

maintainability, through a forced uni-directional data-flow testability/composability, since it is always possible to mock/override a value robustness, as it is harder to forget to handle the update scenario of a model/widget

个人总结:提高项目可维护性,使结构更加清晰,重要的是提供单向数据流,实现更小单元的刷新,提升性能。

接下来就用 Provider 改造下默认的技术Demo。

添加 Provider 依赖

在 pubspec.yaml 中增加 provider: ^3.1.0 ,然后点击 右上角 Packages get 就可以了,这里说明下:

^ 表示适配和当前大版本一致的版本,~ 表示适配和当前小版本一致的版本,所以这里的 ^ 可以不要。

dependencies:
  flutter:
    sdk: flutter

  ...
  
  # Provider https://github.com/rrousselGit/provider
  provider: ^3.1.0

创建 ChangeNotifier

import 'package:flutter/foundation.dart';

class CounterNotifier with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

使用 Provider.of 实现

  • 使用 ChangeNotifierProvider.value 创建 Widget 持有 value (CounterNotifier())
  • 使用 Provider.of 获取 value (CounterNotifier())
import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/CounterNotifier.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 创建 Widget 持有 CounterNotifier 数据
    return ChangeNotifierProvider.value(
      value: CounterNotifier(),
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ProvidePage(title: 'Provider Demo Home Page'),
      ),
    );
  }
}

class ProvidePage extends StatelessWidget {
  final String title;

  ProvidePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    // 获取 CounterNotifier 数据
    final counter = Provider.of<CounterNotifier>(context);
    
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

这样就完成了使用 Provider 对默认计数 Demo 改造,使用 Provider.of 当ChangeNotifier 中调用 notifyListeners 时每次会重新调用 Widget 中的 build

示例1

使用 Consumer 实现

这里实现个稍微复杂点的,创建 Page1 显示 current count,然后创建 Page2 实现官方默认点击计数,看看 Provider 状态管理效果。

构建 MyApp

  1. 这里使用 MultiProvider 添加多个 Provider,当然也可以使用注释掉的嵌套方式,愿意的话
  2. Provider.value(value: 36) 用作字体大小
  3. ChangeNotifierProvider.value(value: CounterNotifier()) 用作 count 计数
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
//    return Provider.value(
//      value: 36,
//      child: ChangeNotifierProvider.value(
//        value: CounterNotifier(),
//        child: MaterialApp(
//          title: 'Privoder Demo',
//          theme: ThemeData(
//            primarySwatch: Colors.blue,
//          ),
//          home: Page1(),
//        ),
//      ),
//    );
    return MultiProvider(
      providers: [
        Provider.value(value: 36),
        ChangeNotifierProvider.value(value: CounterNotifier())
      ],
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Page1(),
      ),
    );
  }
}

构建 Page1

  1. 使用 Provider.of(context).toDouble() 获取文字大小
  2. 使用 Provider.of(context) 获取计数
  3. 使用 Colum 列表方式展示计数和跳转页面按钮
  4. 当 build的时候 print('rebuild page 1');
class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //获取文字大小
    final size = Provider.of<int>(context).toDouble();
    // 获取计数
    final counter = Provider.of<CounterNotifier>(context);
    // 调用 build 时输出
    print('rebuild page 1');
    return Scaffold(
      appBar: AppBar(
        title: Text('Page1'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 显示计数
            Text(
              'Current count: ${counter.count}',
              // 设置文字大小
              style: TextStyle(
                fontSize: size,
              ),
            ),
            SizedBox(
              height: 50,
            ),
            // 跳转 Page2
            RaisedButton(
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => Page2()),
              ),
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

构建 Page2

  1. 使用 Consumer2 获取计数对象和文字大小
  2. 分别使用 print('rebuild page 2') 和 print('rebuild page 2 refresh count') 输出调用相应 build 日志
  3. 使用 Consumer 获取计数对象,在点击 button 时调用 CounterNotifier 的 increment 方法
class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('rebuild page 2');
    return Scaffold(
      appBar: AppBar(
        title: Text('Page2'),
      ),
      body: Center(
        child: Consumer2<CounterNotifier, int>(
            builder: (context, counter, size, _) {
          print('rebuild page 2 refresh count');

          return Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '${counter.count}',
                style: TextStyle(
                  fontSize: size.toDouble(),
                ),
              ),
            ],
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 不需要监听改变(listen: false 不会重新调用build)
          Provider.of<CounterNotifier>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

示例2

2019-09-14 22:21:31.393 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 1 2019-09-14 22:21:31.406 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 2 refresh count

通过这个日志可以看出,使用 Consumer 可以实现局部刷新,所以它的性能更优,应该优先选用。但是 Consumer 只有 Consumer ~ Consumer6,没有多的是实现。如果需要则只有自己实现。

一点建议:不要吝啬逗号,这会让你的代码结构看起来更爽

provider 提供了几种不同类型得 XXProvider ,使用方法都大同小异

name description
Provider 最基本得 provider. 携带一个 value 并暴露它.
ListenableProvider Listenable 对象的特定 Provider 。ListenableProvider将监听对象,以便在调用侦听器时重建依赖它的 Widgets。
ChangeNotifierProvider 一个具体的 ListenableProvider 监听 ChangeNotifier 。它会在需要时自动调用ChangeNotifier.dispose。
ValueListenableProvider 监听 ValueListenable 并仅暴露 ValueListenable.value。
StreamProvider 监听 Stream 并暴露最新发出的值。
FutureProvider 携带一个 Future,并在 Future 完成时更新依赖

以上就是 Provider 使用,最后奉上Demo地址