Flutter tutorial: Architect your app using Provider and Stream
Flutter 104 by Scott Stoll
Provider is the recommended way to do State Management for apps of all sizes. -–Chris Sells – Product Manager, Flutter. June 19, 2019
That's a direct quote from Chris, from when he was on #HumpDayQandA. We had asked him if the Flutter team recommended any one method of State Management approach over others, and yes, they do. In fact, they recommend Remi Rousselet's Provider over their own version of it, which was called Package Provider. They even said so in their talk, “Pragmatic State Management”, at I/O 2019 https://www.youtube.com/watch?v=d_m5csmrf7I
So what is a Provider?
In short, Provider is like a way to use InheritedWidget that humans can understand. You remember InheritedWidget, right? I like to call it “Flutter's Monad”:
If you understand, no explanation is necessary.
If you don't understand, no explanation can help.
Provider looks daunting when you first dig into it. I mean, you could make a word cloud out of all the classes you can use with it:
When someone first looks at this, they often get intimidated. But, after Remi kindly took a lot of his time to help me understand (and didn't laugh at me) I realized something. This all becomes a lot easier to understand when you stop thinking there are a lot of classes and start thinking there are only three jobs that need to be done.
I like to explain it as a firefighting team. There are 3 jobs:
- Sound the alarm (Notify people things Changed, that we now have a fire)
- If door is closed, so someone has to open it and Provide access (expose the fire)
- The other people are the Consumers of fire, and a Provider.of water
That's it. You have three jobs, and each of those classes can do one of them. Which classes you use will depend on what you need to do in your situation.
These handle notifying the Providers that something has changed. Which one you use will depend on what you're trying to listen for. A ChangeNofier is going to notify of any changes in the class, as it is tracking the object as a whole. You can only use one of these with any one Type/Object/Model. Here are the main two most people use, but these aren't the only way to do it:
- “A class that can be extended or mixed in that provides a change notification API using [VoidCallback] for notifications. [ChangeNotifier] is optimized for small numbers (one or two) of listeners.” - change_notifier.dart
- ChangeNotifier uses the notifyListeners function whenever there is any change in the class/model/object.
- “A [ChangeNotifier] that holds a single value. When [value] is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.”
These handle putting an access point into your tree, exposing the objects you need to use. You place it above where you're going to use it. After all, you can't use it if it's not there to use. It has been recommended to keep these as far down the tree as possible, to avoid “polluting the scope”. An important thing to know is you can't use more than one of these for the same type, in the same scope. The widgets using the data will only grab that data from the nearest one, and so never see any others.
An important note regarding Context: When you create a Provider, it's declared within the BuildContext of the build method it's in. This means the context is created before the Provide, so the Provider can't possibly be part of the information contained in that context since it didn't exist yet when that context was created. But you can't use a Provider unless it's in the context where you're trying to use it.
Okay, this time in human. In short, you need a new context; one with information about the Provider in it.
- You get that by running a build method between where you create the Provider and wherever it is you're trying to use it.
- You can do that by creating a new file (Widget) with its own build method, then calling it as one of the children between your Provider and where you use it.
- This build method creates a new BuildContext that contains information about your Provider.
- With information about your Provider in this new context, you can now access it from within this new scope.
Here are some of the Providers you can work with:
- This manages the lifecycle of the value it provides, using a Provider pattern. “It is usually used to avoid making a [StatefulWidget] for something trivial, such as instantiating a BLoC.” - provider.dart
- Many times when you try to use a plain Provider, the IDE will poke you and say to use something more specific. This is pointed out in a humorous way in the doc comments:
A sanity check to prevent misuse of
[Provider]when a variant should be used.
- provider.dart, line 266
- “Listens to a [ChangeNotifier], expose it to its descendants and rebuilds dependents whenever the [ChangeNotifier.notifyListeners] is called.” - change-notifier_provider.dart
- Warning: There are many things to know about what to do, and what to not do, when using a ChangeNotifierProvider. I strongly recommend you read all the doc comments, they contain crucial information that will save you a lot of pain.
- This is used a lot, and for good reason. It's basically a list of all the different Providers being used within its scope. Without this, you would have to nest your Providers, with one being the child of another and another, making things into a real mess.
Other Providers include:
- ListenableProvider (the superclass of ChangeNotifierProvider)
- ValueListenableProvider (for listening to one value instead of a whole type)
- And more…
Earlier, we said the team was a Consumer of fire and Provider.of water. This is just a mnemonic device, intended to help you remember. What we could have added is that you can use both, and so don't have to worry about Selecting one of the two. Unlike Notifiers and Providers, you can have more than one that Consumes. You could even have more than one listen to the same Provider, if that's what you want to do.
- “Obtains the nearest [
Provider<T>] up its widget tree and returns its value.” - provider.dart
- Remember, T can be anything. It's your model/object/whatever it is that's calling notifyListeners().
- Notice that it says nearest. Again, you can only use one Provider per type because Provider.of is going to stop looking as soon as finds the nearest one on its way up the tree.
- “The widget [Consumer] doesn't do any fancy work. It just calls [Provider.of] in a new widget, and delegate its
buildimplementation to [builder].” - consumer.dart
- It's just syntactic sugar for Provider.of but the funny thing is I think Provider.of is simpler to use. Your mileage may vary.
- “An equivalent to [Consumer] that can filter updates by selecting a limited amount of values and prevent rebuild if they don't change.” - selector.dart
- In human: It's just a Consumer with a filter slapped on it. It only rebuilds if the thing that changed is something you've added to its list. If the thing that changed isn't on your list, your child won't rebuild.
And that, ladies and gentlemen, is an overview of Provider, in a nutshell.
You can stalk the author on Twitter at @scottstoll2017