原文在这里。
尽管官网 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 的教程心愿对你有帮忙。