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
- 这里需要混入
ChangeNotifier - 写一个增加的方法,然后需要调用
notifyListeners();这个方法是通知用到Counter对象的widget刷新用的。 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(),
);
}
}
ChangeNotifierProvider调用value()方法,里面传出notifier和childnotifier设置了默认的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),
),
);
}
}
- 用
Provider.of<Counter>(context).count获取_count的值,Provider.of<T>(context)相当于Provider去查找它管理的Counter(1) - 用
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…