Making sense of all those Flutter Providers

Suragch
Suragch
Oct 30 · 10 min read
ErikaWittlieb from Pixabay

Although the official Flutter site (in Simple app state management) says that the Provider package “is easy to understand,” I haven’t found that to always be the case. I think the main cause is the sheer number of Provider types there are:

  • Provider
  • ListenableProvider
  • ChangeNotifierProvider
  • ValueListenableProvider
  • StreamProvider
  • FutureProvider
  • MultiProvider
  • ProxyProvider
  • ChangeNotifierProxyProvider
  • and more

I just want to manage my app state in an easy way. Why are there so many choices? Which one am I supposed to use? Where do I start?

The purpose of this article is to help you understand what each of the main Provider types are for. I’ll give a minimal example of how each one is used. Then when you understand the differences, you can decide for yourself if and how you want to use the Provider package to manage app state in your project.

Setup

I’m going to use the following layout in the examples below:

This is the meaning:

  • The “Do something” button represents any app event that changes the app state.
  • The “Show something” Text widget represents any part of the UI that needs to display the app state.
  • The green rectangle on the left and the blue rectangle on the right represent two different parts of the widget tree. They are there to emphasize that an event and the UI it updates may be in any part of the app.

Here is the code:

The examples below will also assume that you already have the provider package in your pubspec.yaml file:

dependencies:
provider: ^3.1.0

And that you are importing it where needed:

import 'package:provider/provider.dart';

Provider

As you might imagine, is the most basic of the Provider widget types. You can use it to provide a value (usually a data model object) to anywhere in the widget tree. However, it won’t help you update the widget tree when that value changes.

Say your app state is in a model like this:

class MyModel { 
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}

You can provide that model to the widget tree by wrapping the top of the tree with the widget. You can get a reference to the model object by using the widget.

Find the and two widgets in the code below:

Running this gives the following result:

Notes:

  • The UI was built with the “Hello” text that came from the model.
  • Pressing the “Do something” button will cause an event to happen on the model. However, even though the model’s data got changed, the UI wasn’t rebuilt because the widget doesn’t listen for changes in the values it provides.

ChangeNotifierProvider

Unlike the basic widget, listens for changes in the model object. When there are changes, it will rebuild any widgets under the .

In the code, change to . The model class needs to use the mixin (or extend it). This gives you access to and any time you call that, the will be notified and the Consumers will rebuild their widgets.

Here is the complete code:

Now when you press the “Do something” button, the text changes from “Hello” to “Goodbye”.

Notes:

  • In most apps your model class will be in its own file and you’ll need to import in order to use . I’m not really a fan of that because that means your business logic now has a dependency on the framework, and the framework is a detail. But I’m willing to live with it for now.
  • The widget rebuilds any widgets below it whenever gets called. The button doesn’t need to get updated, though, so rather than using a , you can use and set the listener to false. That way the button won’t be rebuilt when there are changes. Here is the button extracted into its own 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();
},
);
}
}

In the examples below I’m just going to leave the button as it was, though. That is, using the .

FutureProvider

is basically just a wrapper around the widget. You give it some initial data to show in the UI and also provide it a Future of the value that you want to provide. The listens for when the Future completes and then notifies the Consumers to rebuild their widgets.

In the code below I used an empty model to give some initial data to the UI. I also added a function to return a new model after 3 seconds. This is what the waits for.

Like the basic , does not listen for any changes within the model itself. I show that in the code below by making the “Do something” button change the model after 2 seconds. There is no effect on the UI.

Here is the full code:

Notes:

  • The tells the to rebuild after completes.
  • Press hot restart to rebuild the app with initial values.
  • Note that pressing the “Do something” button does not update the UI, even after the Future completes. If you want that kind of functionality, then just use the from the last section.
  • Your use case for might be to read some data from a file or the network. But you could also do that with a . In my unexpert opinion, is not significantly more useful than a . If I need a provider then I would probably use a , and if I don’t need a provider then I would probably use a . I’m happy to update this if you would like to add a comment, though.

StreamProvider

is basically a wrapper around a . You provide a stream and then then the Consumers get rebuilt when there is an event in the stream. The setup is very similar to the above.

You should consider the values that are emitted from the stream to be immutable. That is, the doesn’t listen for changes in the model itself. It only listens for new events in the stream.

The code below shows a that provides a stream of model objects. Here is the full code:

Notes:

  • The tells the to rebuild after when there is a new stream event.
  • Press hot restart to rebuild the app with initial values.
  • Note that pressing the “Do something” button does not update the UI. If you want that kind of functionality, then just use a . In fact, you could have a stream in your model object and just call . You wouldn’t need a at all in that case.
  • You could use a to implement the BLoC pattern.

ValueListenableProvider

Feel free to scroll past this one. It’s kind of like . . . but more complicated . . . and without any obvious added value.

If you have a like this,

class MyModel {  ValueNotifier<String> someValue = ValueNotifier('Hello');  void doSomething() {
someValue.value = 'Goodbye';
}
}

then you can listen to any changes in it with . However, if you want to call a method on the model from the UI, then you also need to provide the model. Thus, in the following code you can see a provides to a that gives the in to the .

Here is the full code:

Notes:

  • Pressing the “Do something” button makes “Hello” change to “Goodbye” because of the.
  • It would probably be better to use rather than a at the top of the widget tree. Otherwise we are rebuilding the whole tree every time.
  • gives to both the and to the “Do something” button closure.
  • The for the widget knew to get its value from the because the types matched.
  • Seriously, making this example was a pain, especially trying to insert a at the top of the widget tree and getting all the brackets and parentheses mixed up. Using a (see below) would have improved it, though. Or just save yourself some pain and use a .

ListenableProvider

You would only use this if you need to build your own special provider. Even the documentation says that you probably want a instead. So I am going to ignore for now, though I may update this section in the future if I find a good use case.

MultiProvider

Up to now our examples have only used one model object. If you need to provide a second type of model object, you could nest the providers (similarly to what I did in the example above). However, all that nesting is messy. A neater way to do it is to use a .

In the following example there are two different models that are provided with two .

Here is the full code. It is a little long. Just scroll down noticing the , the , and the two model classes at the bottom:

Notes:

  • Pressing the first “Do something” button will change the “Hello” to “Goodbye”. Pressing the second “Do something” button will change the “0” to “5”.
  • There isn’t much different between this and the single . The way that the different Consumers get the right model is by the type they indicate. That is, gets , and gets .

ProxyProvider

What if you have two models that you want to provide, but one of the models depends on the other one? In that case you can use a . A takes the value from one provider and lets it be injected into another provider.

The way you set up a can be confusing at first, so let me add a little explanation about that.

The basic has two types in the angle brackets. The first type is what the second type depends on. That is, it is a model that has already been provided by another . It gets injected into the second model type in the closure. The third parameter () stores the previous built value, but we don’t use that here. We just pass into the constructor of .

Here is the full code for the example:

Notes:

  • The text starts of saying “Hello”.
  • When you press the “Do something” button, this has change the text to “Goodbye”. notifies its listener () and the UI gets rebuilt with the new text.
  • When you press the “Do something else” button, takes (that was injected by the ) and changes its text to “See you later”. Because notifies its listeners of changes, the UI again gets updated. If had its own data that got changed, the UI would not be updated because does not listen for changes. You would need a for that.
  • was sufficiently confusing for me. has even more special caveats and warnings. For that reason I am not going to add an example for it at this time. You can check out the documentation, though.
  • I am in agreement with FilledStacks that the GetIt package is an easier way to handle dependency injection than .

Provider builder and value constructors

Before I conclude, I want to explain one more thing that was confusing as I was learning to use Provider.

Most (if not all) of the Provider widgets have two kinds of constructors. The basic constructor takes a function in which you create your model object. We did that in most of the examples above.

Provider<MyModel>(
builder: (context) => MyModel(),
child: ...
)

You can see that the object was created in the function.

If your object has already been created and you just want to provide a reference to it, then you can use the named constructor called :

final myModel = MyModel();...Provider<MyModel>.value(
value: myModel,
child: ...
)

Here was previously created and was just passed in as a reference.

Conclusion

After all this, my advice is that you can ignore most of the classes in the Provider package. Just learn how to use and . Every now and then you might use a widget if you don’t need to update the UI. The logic for Futures and Streams can all be put into your model class that notifies . No need for or . Most of the time you won’t need a either if you have a view model for each screen or page. And for injecting dependencies in your view models, GetIt will handle that. No need to worry about . This post gives some very specific help with what I have described here.

Even though I am telling you to ignore most of the Provider package, I do like it at its core. When you just focus the , it really is an easy way to handle app state and architecture.

Thanks

I’ve read a lot of different explanations and tutorials about Provider, but special thanks for the inspiration from these sources:

Flutter Community

Articles and Stories from the Flutter Community

Suragch

Written by

Suragch

A Flutter and Dart developer with a background in Android and iOS. Follow me on Twitter @suragch1 for new article notifications.

Flutter Community

Articles and Stories from the Flutter Community

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade