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