Flutter状态管理之Provider的理解使用

5,665 阅读3分钟

首先,当然可以看看,官方文档翻译参考

一、为什么需要Provider管理状态

数据变化,数据共享,需要Provider

  • Flutter的代码,是响应式/声明式的。以前安卓/iOS的代码,是命令式的。
  • 响应式的代码,基本都需要进行状态管理,也可以理解为数据共享
  • 界面、数据是变化的,就需要管理的,简单的直接在StatefulWidget进行管理就好,复杂的就是用Provider之类来管理

简单和复杂数据的例子

数据的变化,怎么算简单的呢?——比如一个 PageView 组件中的当前页面、一个复杂动画中当前进度、一个 BottomNavigationBar 中当前被选中的 tab。这些在widget 树,其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变。

什么数据变化需要Privider来管理呢?举例子,比如,用户选项、登录信息、一个社交应用中的通知、一个电商应用中的购物车、一个新闻应用中的文章已读/未读状态

.
.

Flutter的状态管理有ReduxRxhooksScopedModel, 和Provider等,其中Provider是官方推荐的。

如果你不了解其他的,那肯定是官方推荐的优先。

二、Provider的使用

嗯,Provider是官方推荐的。不熟悉的,可以看看这两篇先。

Provider官方文章

使用 Provider 管理 Flutter 应用状态


二.1 使用步骤

引入 provider

dependencies:
  provider: ^3.1.0

关于 provider 的使用可以简单理解为3步:

  • 创建继承自 ChangeNotifier 的共享类
  • 设置数据
  • 获取数据,2种方式,分别是Provider.of(context) 和 Consumer

很简单吧~

.
.
.

二.2 最简单的例子 Provider.of(context) 方式

以下代码:

  • 创建共享类,添加一个增长数据的方法,调用就刷新
  • 调用数据
  • build重新调用,刷新ui

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';
import 'package:flutter/foundation.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 测试页面'),
      ),
    );
  }
}

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(
              '按下按钮,使数字增长:',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}


//  核心:继承自ChangeNotifier
// 这种文件本来应该单独放在一个类文件连的
class CounterNotifier with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  increment() {
    _count++;
    // 核心方法,通知刷新UI,调用build方法
    notifyListeners();
  }
}

.
.

效果:

注意

  • 使用 Provider.of 当ChangeNotifier 中调用 notifyListeners 时每次会重新调用 Widget 中的 build

大概如此了。你可能谁说,直接 setState 就好了。不,这肯定不一样,setState太不灵活了。

.
.
.

二.3 例子 Consumer 方式

例子:在页面一设置1个值,然后在页面2显示出来 (不要问为什么不直接用Navigator,演示演示,只为演示)

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

main() {
  runApp(ChangeNotifierProvider<CounterNotifier>.value(
    value: CounterNotifier(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MultiProvider(
      providers: [
        Provider.value(value: 36),
        ChangeNotifierProvider.value(value: CounterNotifier())
      ],
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Page1(),
      ),
    );

 }
}

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'),
            ),
          ],
        ),
      ),
    );
  }
}


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),
      ),
    );
  }
}


class CounterNotifier with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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


.

注意

  • 1、在Page2中,使用了Consumer
  • 2、Provider.of的listen如果为false,不会重新调用build

.
页面2设置数值,返回页面1时,页面1显示的是页面2的数值

.
.

.

对比
  • 触发者(Provider.of):如果只是需要获取到数据model,不需要监听变化(例如点击按钮),推荐使用Provider.of(context, listen: false)来获取数据model。
  • 监听者(推荐使用Consumer):推荐使用Consumer。

.
.

END

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