flutter学习--Provider

220 阅读5分钟

Provider 从名字上就很容易理解,它就是用于提供数据,无论是在单个页面还是在整个 app 都有它自己的解决方案,我们可以很方便的管理状态。可以说,Provider 的目标就是完全替代 StatefulWidget。

说了很多还是很抽象,我们先一起做一个最简单的例子。

第一步:添加依赖

在pubspec.yaml中添加Provider的依赖。

provider: ^2.0.1+1

pub地址:pub.dev/packages/pr…

第二步:创建Model

Provider提供的Model,其实和State作用一样的,这里为了区别,一般都使用Model概念。

import 'package:provider/provider.dart';
class Counter with ChangeNotifier {//1
  int _count;
  Counter(this._count);

  void add() {
    _count++;
    notifyListeners();//2
  }
  get count => _count;//3
}

简单的一个Counter对象,里面只有一个字段_count

  1. 这里需要混入ChangeNotifier
  2. 写一个增加的方法,然后需要调用notifyListeners();这个方法是通知用到Counter对象的widget刷新用的。
  3. get方法

第三步,使用ChangeNotifierProvider

通常main()方法是这么写

main() {
  runApp(MyApp());
}

我们要监听改变就要在MyApp()外面套一层,这个是全局的,于是如下

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

main() {
  runApp(ChangeNotifierProvider<Counter>.value(//1
    notifier: Counter(1),//2
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      title: "Provider",
      home: HomePage(),
    );
  }
}

如果添加多个Provider

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stateresearch/model/CountProviderModel.dart';
import 'package:stateresearch/model/ListProviderModel.dart';
import 'package:stateresearch/pages/ProviderPageTwo.dart';

void main() {
  runApp(
    // 多个 Model 需要全局共享时,需要用 MultiProvider 包一层
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CountProviderModel()),
        ChangeNotifierProvider(create: (_) => ListProviderModel()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter状态管理',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProviderPageTwo(),
    );
  }
}
  1. ChangeNotifierProvider调用value()方法,里面传出notifierchild
  2. notifier设置了默认的Counter(1)

当然Provider不止提供了ChangeNotifierProvider,还有Provider,ListenableProvider,ValueListenableProvider,StreamProvider,
具体可以看wiki.
如果想管理多个对象可以用MultiProvider

第四步:在子页面中获取状态

Provider.of(context)

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

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
        actions: <Widget>[
          FlatButton(
            child: Text("下一页"),
            onPressed: () =>
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return SecondPage();
                })),
          ),
        ],
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),//1
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();//2
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
  1. Provider.of<Counter>(context).count获取_count的值,Provider.of<T>(context)相当于Provider去查找它管理的Counter(1)
  2. Provider.of<Counter>(context).add();调用Counter()中的add()方法

同样第二个页面也这样写,如下

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("SecondPage"),
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),//1
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();//2
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

这样,当每个页面都点击+号按钮时,_count便会+1,同时通知并更新到使用它的地方。

完整代码,copy后可直接运行

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      title: "Provider",
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
        actions: <Widget>[
          FlatButton(
            child: Text("下一页"),
            onPressed: () =>
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return SecondPage();
                })),
          ),
        ],
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text(Provider.of<String>(context)),
      ),
      body: Center(
        child: Text("${Provider.of<Counter>(context).count}"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context).add();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class Counter with ChangeNotifier {

  int _count;

  Counter(this._count);

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

  get count => _count;
}

Consumer

这里我们要介绍第二种方式,使用 Consumer 获取祖先节点中的数据。

Consumer 使用了 Builder 模式,收到更新通知就会通过 builder 重新构建。Consumer<T> 代表了它要获取哪一个祖先中的 Model。

Consumer 的 builder 实际上就是一个 Function,它接收三个参数 (BuildContext context, T model, Widget child)

  • context: context 就是 build 方法传进来的 BuildContext 在这里就不细说了,如果有兴趣可以看我之前这篇文章 Flutter | 深入理解BuildContext
  • T:T也很简单,就是获取到的最近一个祖先节点中的数据模型。
  • child:它用来构建那些与 Model 无关的部分,在多次运行 builder 中,child 不会进行重建。

然后它会返回一个通过这三个参数映射的 Widget 用于构建自身。

在这个浮动按钮的例子中,我们通过 Consumer 获取到了顶层的 CounterModel 实例。并在浮动按钮 onTap 的 callback 中调用其 add 方法。

而且我们成功抽离出 Consumer 中不变的部分,也就是浮动按钮中心的 Icon 并将其作为 child 参数传入 builder 方法中。

Consumer2

现在我们再来看中心的文字部分。这时候你可能会有疑惑了,刚才我们讲的 Consumer 获取的只有一个 Model,而现在 Text 组件不仅需要 CounterModel 用以显示计数器,而且还需要获得 textSize 以调整字体大小,咋整捏。

遇到这种情况你可以使用 Consumer2<A,B>。使用方式基本上和 Consumer<T> 一致,只不过范型改为了两个,并且 builder 方法也变成了 Function(BuildContext context, A value, B value2, Widget child)

我勒个去...假如我要获得 100 个 Model,那岂不是得搞个 Consumer100 (???黑人问号.jpg)

然而并没有 😏。

从源码里面可以看到,作者只为我们搞到了 Consumer6。emmmmm.....还要要求更多就只有自力更生喽。

区别

我们来看 Consumer 的内部实现。

@override
  Widget build(BuildContext context) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }

可以发现,Consumer 就是通过 Provider.of<T>(context) 来实现的。但是从实现来讲 Provider.of<T>(context)比 Consumer 简单好用太多,为啥我要搞得那么复杂捏。

实际上 Consumer 非常有用,它的经典之处在于能够在复杂项目中,极大地缩小你的控件刷新范围Provider.of<T>(context) 将会把调用了该方法的 context 作为听众,并在 notifyListeners 的时候通知其刷新。

举个例子来说,我们的 FirstScreen 使用了 Provider.of<T>(context) 来获取数据,SecondScreen 则没有。

  • 你在 FirstScreen 中的 build 方法中添加一个 print('first screen rebuild');
  • 然后在 SecondScreen 中的 build 方法中添加一个 print('second screen rebuild');
  • 点击第二个页面的浮动按钮,那么你会在控制台看到这句输出。 first screen rebuild

首先这证明了 Provider.of<T>(context) 会导致调用的 context 页面范围的刷新。

那么第二个页面刷新没有呢? 刷新了,但是只刷新了 Consumer 的部分,甚至连浮动按钮中的 Icon 的不刷新我们都给控制了。你可以在  Consumer 的 builder 方法中验证,这里不再啰嗦

假如你在你的应用的 页面级别 的 Widget 中,使用了 Provider.of<T>(context)。会导致什么后果已经显而易见了,每当其状态改变的时候,你都会重新刷新整个页面。虽然你有 Flutter 的自动优化算法给你撑腰,但你肯定无法获得最好的性能

所以在这里我建议各位尽量使用 Consumer 而不是 Provider.of<T>(context) 获取顶层数据。

跟深入的俩节参考下面的链接,写得非常好。 参考出处www.jianshu.com/p/c89eca674…