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