原文在这里。
虽然官方Flutter站点(状态管理入门app)说Provider“非常容易理解”,我(作者)可不这么认为。我想是因为Provider的种类有点多:
- Provider
- ListenableProvider
- ChangeNotifierProvider
- ValueListenableProvider
- StreamProvider
- FutureProvider
- MultiProvider
- ProxyProvider
- ChangeNotifierProxyProvider
- 更多
我只想用最简单的方法管理我的app的状态。为什么有这么多的选择?我应该用哪一个呢?从哪里开始呢?
本文的目的就是帮你理解主要的Provider类型是怎么用的。我会给每一个Provide类型一个简单的例子。帮助你理解,之后你就可以自己决定用哪个来管理你的app的状态了。
准备
我的例子会使用这样的布局
也就是:
- 那个“Do something”按钮代表改变app状态的事件。
- 那个“Show something”文本Widget代表显示app state的UI
- 左边的绿色方框和右边的蓝色的方框代表了widget树的不同部分。用来强调一个事件和与这个事件所更改的状态相关的更新的UI。
这里是代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: RaisedButton(
child: Text('Do something'),
onPressed: () {},
),
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Text('Show something'),
),
],
),
),
);
}
}
这些例子需要你安装Provider包
dependencies:
provider: ^4.0.1
而且已经import到了需要的地方:
import 'package:provider/provider.dart';
Provider
如你所想,Provider
是最基本的Provider类型。你可以用它来给widget树的任何地方提供一个值(一般是data model对象)。然而,值发生变化的时候是不会帮你更新widget树的。
假设你的app状态在一个model里:
class MyModel {
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
你可以用Provider包装顶层Widget,给widget树提供数据。使用Consume
widget来获得这个数据的引用。
你可以在西面的代码找到Provider
和两个Consume
widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<MyModel>( // <--- Provider
create: (context) => MyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
class MyModel { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
运行代码,你会看到这个结果:
NOTES:
- UI上显示了mode对象的Hello。
- 点击“Do something”按钮会引发一个导致model数据改变的事件。然而,即使model的数据改变了,UI也不会发生更改。因为
Provider
不会监听model对象的数据变更。
ChangeNotifierProvider
与基本的Provider
widget不同,ChangeNotifierProvider
会监听model对象的变化。当数据发生变更,它会重绘Consumer
的子widget。
在上面的代码里,把Provider
换成ChangeNotifierProvider
。Model类也需要使用ChangeNotifier
mixin(or 扩展它)。这样你可以使用notifyListeners()
方法。当你调用这个方法的时候,ChangeNotifierProvider
就会收到通知,Consumer
widget就会重绘它的子组件了。
下面是完整代码:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
create: (context) => MyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}
现在当你点击了“Do something”按钮,文本会从“Hello”变成“Goodbye”。
NOTE:
- 多数的app里,model类都在各自的文件里。你需要在这些文件里import flutter/foundation.dart,这样才能用
ChangeNotifier
。我不太喜欢这样,因为这样的话就表明你的业务逻辑里包含了一个framework的依赖,而且这个framework还是一个和逻辑无关的“细节”。我们暂时先这样。 - 当
notifyListeners()
方法后,Consumer
widget会重绘它的子树的所有widget都会重绘。按钮没有必要更新,所以相对于Consumer
你可以用Provider.of
,并把listener设置为false。这样model有变化,按钮也不会更新了。这里是修改了之后的按钮widget。
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final myModel = Provider.of<MyModel>(context, listen: false);
return RaisedButton(
child: Text('Do something'),
onPressed: () {
myModel.doSomething();
},
);
}
}
下面的代码还是老样子,使用Consumer
组件。
FutureProvider
FutureProvider
基本上是FutureBuilder
的一个封装widget。你可以设置一些UI显示的初始数据,然后可以给一个Future值。FutureProvider
会在Future完成的时候通知Consumer重绘它的widget子树。
在下面的代码里我给UI提供了一个空的model。我也加了一个方法,会在3秒钟后返回一个新的model对象。这也是FutureProvider
在等待的。
和基本的Provider一样,FutureProvider
不会监听model本身的任何更改。在下面的代码里会表明这一点。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureProvider<MyModel>( // <--- FutureProvider
initialData: MyModel(someValue: 'default value'),
create: (context) => someAsyncFunctionToGetMyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
Future<MyModel> someAsyncFunctionToGetMyModel() async { // <--- async function
await Future.delayed(Duration(seconds: 3));
return MyModel(someValue: 'new data');
}
class MyModel { // <--- MyModel
MyModel({this.someValue});
String someValue = 'Hello';
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 2));
someValue = 'Goodbye';
print(someValue);
}
}
NOTE:
FutureProvider
会在Futuer<MyModel>
完成后通知Consumer
重绘。- 点击Hot restart重置app
- 注意,点击“Do something”按钮不会更新UI,即使是在Future完成了之后。如果你想让UI可以重绘,使用
ChangeNotifierProvider
。 FutureProvider
的使用情况一般是在处理网络或者文本读取数据的时候。但是,也可以使用FutureBuilder
来达到同样的目的。以我(作者)不成熟的看法,FutureProvider
没有比FutureBuilder
更有用。如果我需要一个provider,我基本上会用ChangeNotifierProvider
,如果我不需要provider的话可能会用FutureProvider
。
StreamProvider
StreamProvider
是StreamBuilder
的一个封装。你提供一个stream,然后Consumer
会在stream收到一个事件的时候更新它的widget子树。这和上面的FutureProvider
非常相似。
你可以把stream发送的值当做不可修改的。也就是说,StreamProvider
不会监听model的变化。只会监听stream里的新事件。
代码如下:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamProvider<MyModel>( // <--- StreamProvider
initialData: MyModel(someValue: 'default value'),
create: (context) => getStreamOfMyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
Stream<MyModel> getStreamOfMyModel() { // <--- Stream
return Stream<MyModel>.periodic(Duration(seconds: 1),
(x) => MyModel(someValue: '$x'))
.take(10);
}
class MyModel { // <--- MyModel
MyModel({this.someValue});
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
NOTE:
StreamProvider
在收到新的事件之后通知Consumer
重绘- 使用hot restart重置app
- 点击“Do something”不会重绘UI。如果你想要UI可以更新,那么使用
ChangeNotifierProvider
。事实上,你可以在model里加一个stream,然后调用notifyListeners()
。这样的话完全不需要StreamProvider
了。 - 你可以使用
StreamProvider
实现BLoC模式
ValueListenableProvider
你可以略过这一节,它和ChangeNotifierProvider
基本一样,只是更加复杂一点,而且没有什么额外的好处。。。
如果你有一个这样的ValueNotifier
:
class MyModel {
ValueNotifier<String> someValue = ValueNotifier('Hello');
void doSomething() {
someValue.value = 'Goodbye';
}
}
那么你可以用ValueListenableProvider
来监听这个值的变化。但是,如果你想在UI里面调用model的方法,那么你也需要在provider里provide这个model对象。因此,你会在下面的代码里看到一个Provider
提供了一个MyModel
对象给Consumer
,而这个provider同时也给了ValueListenableProvider
它需要的包含在MyModel
的ValueNotifier
属性someValue
。也就是如果只是监听一个model对象的属性值只需要ValueListenableProvider
,但是你想在UI里调用这个model的方法,那么还要额外写一个Provider
。
完整代码如下:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<MyModel>(// <--- Provider
create: (context) => MyModel(),
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return ValueListenableProvider<String>.value( // <--- ValueListenableProvider
value: myModel.someValue,
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<String>(// <--- String Consumer
builder: (context, myValue, child) {
return Text(myValue);
},
),
),
],
),
),
),
);
}),
);
}
}
class MyModel { // <--- MyModel
ValueNotifier<String> someValue = ValueNotifier('Hello'); // <--- ValueNotifier
void doSomething() {
someValue.value = 'Goodbye';
print(someValue.value);
}
}
不过作者本人对以上的看法后来发生了改变,他推荐大家看这篇文章。
ListenableProvider
如果你要定制一个provider,那么就需要用到这个provider类型。不过文档还是提醒,你也许只是需要一个ChangeNotifierProvider
。所以,这个内容暂时忽略。之后我(作者)会更新这部分。
MultiProvider
到目前为止,我们的例子还是只用过一个model对象。如果你需要另外的一个model对象,你就要嵌套provider了(就和在讲解ValueListenableProvider
的时候一样)。然而,嵌套只会造成混乱。一个更好的办法就是使用MultiProvider
。
在下面的例子里会使用ChangeNotifierProvider
提供两种model对象。
下面是全部代码。有点长。只需要注意MultiProvider
,Consumer
和两个model类。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
// SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.red[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.yellow[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, anotherModel, child) {
return Text('${anotherModel.someValue}');
},
),
),
],
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}
class AnotherModel with ChangeNotifier { // <--- AnotherModel
int someValue = 0;
void doSomething() {
someValue = 5;
print(someValue);
notifyListeners();
}
}
NOTES:
- 点击第一个“Do something”按钮,文本会从
Hello
变成Goodbye
。点击第二个“Do something”按钮,文本会从0
变成5
。 - 这和单个的
ChangeNotifierProvider
没有很大的不同。Consumer
通过不同的类型参数获取到对应的model对象。
ProxyProvider
如果你有两个model对象要提供,而且这两个model对象之间还有依赖关系。在这个情况下你可以使用ProxyProvider
。一个ProxyProvider
从一个provider里接受对象,然后把这个对象注入另外一个provider。
一开始你会对ProxyProvider
的使用方法有些困惑,这个例子可以帮你加深理解。
MultiProvider(
providers: [
ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
),
ProxyProvider<MyModel, AnotherModel>(
update: (context, myModel, anotherModel) => AnotherModel(myModel),
),
],
基本的ProxyProvider
有两个类型参数。第二个依赖于第一个。在ProxyProvider
的udpate
方法里,第三个参数有anotherModel
存了上次的值,但是我们不在这里使用。我们只是把myModel
当做参数传入AnotherModel
的构造函数里。
完整代码:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
create: (context) => MyModel(),
),
ProxyProvider<MyModel, AnotherModel>( // <--- ProxyProvider
update: (context, myModel, anotherModel) => AnotherModel(myModel),
),
],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething('Goodbye');
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
Container(
padding: const EdgeInsets.all(20),
color: Colors.red[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, anotherModel, child) {
return RaisedButton(
child: Text('Do something else'),
onPressed: (){
anotherModel.doSomethingElse();
},
);
},
)
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething(String value) {
someValue = value;
print(someValue);
notifyListeners();
}
}
class AnotherModel { // <--- AnotherModel
MyModel _myModel;
AnotherModel(this._myModel);
void doSomethingElse() {
_myModel.doSomething('See you later');
print('doing something else');
}
}
NOTES:
- 开始文本显示的是
Hello
。 - 当你点击“Do something”按钮,
MyModel
对象的文本会变成Goodbye
。MyModel
通知了ChangeNotifierProvider
, UI也显示了新的文本。 - 当你点击“Do something else”按钮,
AnotherModel
接收MyModel
(ProxyProvider
注入),之后修改文本为"See you later"。因为MyModel
发生更改的时候通知了listener,所以UI发生了更新。如果AnotherModel
更改了它的值,UI是不会更新的。因为ProxyProvider
不会监听更改。这时候可以使用ChangeNotifierProxyProvider
。 ProxyProvider
还是让我有点迷糊。ChangeNotifierProxyProvider
本身就有更多的更多说明了,所以我不打算介绍更多了。- 我(作者)FilledStack用GetIt比用
ProxyProvider
实现依赖注入更好用。
Provider builder和value constructor
最后还有一件事需要解释。
大部分的provider都有两种构造函数。一个基本的接收一个create
方法,用来新建你的model对象。我们在上面的例子中基本都是这么做的:
Provider<MyModel>(
create: (context) => MyModel(),
child: ...
)
可以看到MyModel
对象是在create
方法创建的。
如果你的对象已经创建好了,你只是想要提供一个它的引用。那么你可以用命名构造方法value
:
final myModel = MyModel();
...
Provider<MyModel>.value(
value: myModel,
child: ...
)
这里MyModel
对象提前就建好了,只提供了一个引用。如果你在initState
方法已经建好了对象,你就可以这么做。
总结
基本上你可以忽略provider包里的大多数类。只记住怎么使用ChangeNotifierProvider
和Consumer
。如果你不想要更新UI,那么就使用Provider
。使用Future和Stream的场景都可以把他们放在你的Model类里面,然后通知ChangeNotifierProvider
。不需要FutureProvider
和StreamProvdier
。大多数的时候不需要MultiProvider
。如果有依赖注入的时候,也可以用GetIt包来实现,不需要用ProxyProvider
。这篇文章有更深入的介绍。
下一步: Riverpod
你也许听说过Riverpod,这是一个构建在provider之上的状态管理库。作者也是同一个作者。如果你对provider的使用还有困惑,那么riverpod只会让你更加困惑。但是,一旦熟悉了这个库,那么它会让一切变得简单。我(作者)建议你试一试。我写了一个Riverpod的教程希望对你有帮助。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。