The original text is here .
Although the official Flutter site ( State Management Starter app ) says that Provider is "very easy to understand", I (the author) don't think so. I think it's because there are a bit more types of Providers:
- Provider
- ListenableProvider
- ChangeNotifierProvider
- ValueListenableProvider
- StreamProvider
- FutureProvider
- MultiProvider
- ProxyProvider
- ChangeNotifierProxyProvider
- More
I just want the easiest way to manage the state of my app. Why are there so many options? Which one should I use? Where to start?
The purpose of this article is to help you understand how the main provider types are used. I'll give a simple example for each Provide type. To help you understand, you can then decide for yourself which one to use to manage the state of your app.
Prepare
My example would use a layout like this
That is:
- That "Do something" button represents the event that changes the state of the app.
- The "Show something" text widget represents the UI that displays the app state
- The green boxes on the left and the blue boxes on the right represent different parts of the widget tree. UI used to highlight an event and updates related to the state changed by the event.
Here is the code:
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'),
),
],
),
),
);
}
}
These examples require you to install the Provider package
dependencies:
provider: ^4.0.1
And it has been imported where it is needed:
import 'package:provider/provider.dart';
Provider
As you can imagine, Provider
is the most basic provider type. You can use it to provide a value (usually a data model object) anywhere in the widget tree. However, it doesn't update the widget tree for you when the value changes.
Assuming your app state is in a model:
class MyModel {
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
You can wrap the top-level widget with a Provider to provide data to the widget tree. Use the Consume
widget to get a reference to this data.
You can find Provider
and two Consume
widgets in the west code:
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);
}
}
Run the code and you will see this result:
NOTES:
- The Hello of the mode object is displayed on the UI.
- Clicking the "Do something" button will trigger an event that causes the model data to change. However, even if the model's data changes, the UI does not change. Because
Provider
will not monitor the data changes of the model object.
ChangeNotifierProvider
Unlike the basic Provider
widget, ChangeNotifierProvider
will listen for changes to the model object. When the data changes, it will redraw the child widget of Consumer
.
In the above code, replace Provider
with ChangeNotifierProvider
. The Model class also needs to use the ChangeNotifier
mixin (or extend it). This way you can use the notifyListeners()
method. When you call this method, ChangeNotifierProvider
will be notified, Consumer
widget will redraw its child components.
Here is the complete code:
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();
}
}
Now when you click the "Do something" button, the text will change from "Hello" to "Goodbye".
NOTE:
- In most apps, the model classes are in their own files. You need to import flutter/foundation.dart in these files to use
ChangeNotifier
. I don't like this very much, because it means that your business logic contains a framework dependency, and this framework is a "detail" that has nothing to do with logic. Let's do that for now. - When
notifyListeners()
method,Consumer
widget will redraw all widgets of its subtree. The button doesn't have to be updated, so instead ofConsumer
you can useProvider.of
and set listener to false. In this way, if the model changes, the button will not be updated. Here is the button widget after modification.
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();
},
);
}
}
The code below is still the same, using the Consumer
component.
FutureProvider
FutureProvider
is basically a wrapper widget for FutureBuilder
. You can set some initial data displayed by the UI, and then you can give a Future value. FutureProvider
will notify the Consumer to redraw its widget subtree when the Future completes.
In the code below I provide an empty model to the UI. I also added a method that returns a new model object after 3 seconds. This is FutureProvider
is waiting for.
Like the base provider, FutureProvider
does not listen for any changes to the model itself. This is shown in the code below.
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
will notifyConsumer
Futuer<MyModel>
when it is finished. - Click Hot restart to reset the app
- Note that clicking the "Do something" button will not update the UI, even after the Future has completed. If you want to make the UI redrawable, use
ChangeNotifierProvider
. -
FutureProvider
is generally used when processing network or text reading data. However,FutureBuilder
can also be used for the same purpose. In my (author's) half-baked opinion,FutureProvider
is no more useful thanFutureBuilder
. If I need a provider, I basically useChangeNotifierProvider
, if I don't need a provider I probably useFutureProvider
.
StreamProvider
StreamProvider
is a wrapper for StreamBuilder
. You provide a stream, and Consumer
will update its widget subtree when the stream receives an event. This is very similar to the above FutureProvider
.
You can treat the value sent by the stream as immutable. That is to say, StreamProvider
will not monitor model changes. Only listen for new events in the stream.
code show as below:
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
Notify after new eventConsumer
redraw - reset the app with hot restart
- Clicking "Do something" doesn't redraw the UI. If you want the UI to be updatable, then use
ChangeNotifierProvider
. In fact, you can add a stream to the model and callnotifyListeners()
. This is completely unnecessaryStreamProvider
. - You can use
StreamProvider
to implement BLoC mode
ValueListenableProvider
You can skip this section, it's basically the same as ChangeNotifierProvider
just a little more complicated and with no added benefit. . .
If you have one like this ValueNotifier
:
class MyModel {
ValueNotifier<String> someValue = ValueNotifier('Hello');
void doSomething() {
someValue.value = 'Goodbye';
}
}
Then you can use ValueListenableProvider
to listen for changes in this value. However, if you want to call the model's method in the UI, then you also need to provide the model object in the provider. Therefore, you will see in the code below a Provider
which provides a MyModel
object to Consumer
, and this provider also gives ValueListenableProvider
It needs the ValueNotifier
property someValue
contained in MyModel
5321a93d3e24a50805ce95f1d00bc9a8---. That is, if you only need to monitor the attribute value of a model object, you only need ValueListenableProvider
, but if you want to call the method of this model in the UI, you need to write an additional Provider
.
The complete code is as follows:
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);
}
}
However, the author's own views on the above have changed later, and he recommends everyone to read this article .
ListenableProvider
If you want to customize a provider, then you need to use this provider type. Still, the documentation reminds you that you might just need one ChangeNotifierProvider
. Therefore, this content is temporarily ignored. I (the author) will update this section later.
MultiProvider
So far, our examples have only used a model object. If you need another model object, you need to nest the provider (as in the explanation ValueListenableProvider
). However, nesting only creates confusion. A better way is to use MultiProvider
.
In the following example ChangeNotifierProvider
will be used to provide two model objects.
Below is the full code. a bit long. Just pay attention to MultiProvider
, Consumer
and two model classes.
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:
- Click the first "Do something" button and the text will change from
Hello
toGoodbye
. Click the second "Do something" button and the text will change from0
to5
. - This is not very different from a single
ChangeNotifierProvider
.Consumer
Get the corresponding model object through different type parameters.
ProxyProvider
If you have two model objects to provide, and there are dependencies between the two model objects. In this case you can use ProxyProvider
. One ProxyProvider
accepts an object from one provider and injects the object into another provider.
At first you will be a little confused about the usage of ProxyProvider
, this example can help you to understand.
MultiProvider(
providers: [
ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
),
ProxyProvider<MyModel, AnotherModel>(
update: (context, myModel, anotherModel) => AnotherModel(myModel),
),
],
The basic ProxyProvider
has two type parameters. The second depends on the first. In the ProxyProvider
udpate
method of ---e0a728371e9212fb7f5d9efc6c2122c3---, the third parameter has anotherModel
which stores the last value, but we don't use it here. We just pass myModel
as a parameter to the constructor of AnotherModel
.
Full code:
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:
- The start text
Hello
. - When you click the "Do something" button, the
MyModel
object's text will becomeGoodbye
.MyModel
notifiesChangeNotifierProvider
and the UI also shows the new text. - When you click the "Do something else" button,
AnotherModel
receiveMyModel
(ProxyProvider
inject), then modify the text to "See you later". The UI is updated because the listener is notified whenMyModel
changes. IfAnotherModel
changes its value, the UI will not update. BecauseProxyProvider
does not listen for changes. At this time,ChangeNotifierProxyProvider
can be used. -
ProxyProvider
still confuses me a bit.ChangeNotifierProxyProvider
itself has more and more instructions, so I'm not going to introduce more. - I (the author) FilledStack uses GetIt better than
ProxyProvider
for dependency injection.
Provider builder and value constructor
One last thing to explain.
Most providers have two types of constructors. A basic receive a create
method to create your model object. We basically do this in the above example:
Provider<MyModel>(
create: (context) => MyModel(),
child: ...
)
You can see that the MyModel
object is created in the create
method.
If your object has already been created, you just want to provide a reference to it. Then you can use the named constructor value
:
final myModel = MyModel();
...
Provider<MyModel>.value(
value: myModel,
child: ...
)
Here MyModel
the object is built ahead of time, and only a reference is provided. You can do this if you have already created the object in the initState
method.
Summarize
Basically you can ignore most classes in the provider package. Just remember how to use ChangeNotifierProvider
and Consumer
. If you don't want to update the UI then use Provider
. For scenarios using Future and Stream, you can put them in your Model class and notify ChangeNotifierProvider
. FutureProvider
and StreamProvdier
are not required. Most of the time MultiProvider
is not needed. If there is dependency injection, you can also use the GetIt package to achieve it, you don't need to use ProxyProvider
. This article has a more in-depth introduction.
Next up: Riverpod
You may have heard of Riverpod, a state management library built on top of providers. The author is also the same author. If you are still confused about the use of providers, then riverpod will only confuse you even more. However, once you get familiar with this library, it makes everything easy. I (the author) suggest you give it a try. I wrote a Riverpod tutorial that I hope will help you.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。