In the previous article,
we have discussed the problem of using setState
to update the app's state in the moderate or complex app, This part will cover the solution to those problems. Let's start.
What we are going to learn in this article
- What is Provider, Why we use it?
- What are the types of Provider?
- How to use Provider in our code?
- What is Consumer & Selector?
1. What is Provider, Why we use it
Provider
is one of the most popular state management solution, created by Remi Rousselet, used for flutter app development.
On the beginner stage, we use setState
to update the app's state, because it is easy, and switching from setState
to other complex techniques like flutter_bloc
or Mobx
is not that easy to follow. ๐
Provider uses InheritedWidget
internally, to manage all complex computations and provide a simple feature to manage our app's state
Why
provider
? We have some other techniques that can be used for managing state. Passing data via constructors is not a good idea. In doing so, all those widgets will rebuild just for passing the data. This may cause the performance issue and our code get too much complicated.
So, what we should do now? We will use the provider
flutter_bloc
&Mobx
uses provider internally ๐ฅณ And now the flutter team also recommends using the provider. So, let's learn about the types of provider
2. What are the types of provider
Provider can handle different type of objects
Complete list of all objects
Name | Description |
Provider | The most basic form of provider. It takes a value and exposes it, whatever the value is. |
ListenableProvider | A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called. |
ChangeNotifierProvider | A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed. |
ValueListenableProvider | Listen to a ValueListenable and only expose ValueListenable.value . |
StreamProvider | Listen to a Stream and expose the latest value emitted. |
FutureProvider | Takes a Future and updates dependents when the future completes. |
So, let's discover more about the categories
Provider
This is a very basic form of Provider
, manages the lifecycle of the value it provides by delegating to a pair of Create
and Dispose
. This can provide any type of value.
class Counter {
int count = 0;
void increment() {
count += 1;
notifyListeners();
}
}
class Stateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<Counter>(
create: (context) => Counter(),
dispose: (context, value) => value.dispose(),
child: ...,
);
}
}
ListenableProvider
ListenableProvider
is a bit different from the other providers in the list, as this provider provides state source
as the value.
Other providers generally provide the single value from the object<T>
, but ListenableProvider
provides the whole model as the value to its dependents.
When the state changes in Listenable, it notifies the provider about the state change. Now provider, via it's InheritedWidget
marks it's listening dependent to rebuild.
In the above example, the whole Counter()
object is being returned from create
, as this value is provided to the dependents, dependents use this value as Counter()
and updates it's state and rebuilds again to show the updated value on the screen.
ChangeNotifierProvider
In the change notifier provider, everything is the same as ListenableProvider
, one additional specification of ListenableProvider
for ChangeNotifier is, it automatically calls ChangeNotifier.dispose
to dispose the provider when needed.
ValueListenableProvider
ValueListenableProvider
gets the value change notification from ValueListenable
, and provides the value to its dependent as state. ValueListenableProvider<T>
receives values of type T from a ValueListenable<T>
.
StreamProvider
StreamProvider gets the events from a subscribed stream and provides these events(values) to its dependent widgets. we can use StreamProvider<T>
to receive events of type T from a Stream<T>
.
StreamProvider<int>(
initialData: 0,
builder: (context) {
return Stream<int>
.periodic(Duration(milliseconds: 100), (count) => count + 1)
.take(100);
},
child: ...
)
In the above example, StreamProvider
emits count
value ranges from 0 to 100, on the interval of 100 milliseconds, dependents can use these events for some purpose.
FutureProvider
FutureProvider
gets the value of a future and provides to its dependents. We can use FutureProvider<T>
to get the Future<T>
value and this value represents the state.
FutureProvider<int>(
initialData: 0,
builder: (context) {
return Future.delayed(Duration(seconds: 4), () => 1);
},
child: ...
),
This, FutureProvider<int>
gives the binary value 0 and 1. In the initial state, it provides the 0, and as soon as future resolves (after 4 seconds)this provides the 1 as a value
3. How to use a provider in our code
Provider has some methods and we will look at 3 major one
context.watch<T>()
, which makes the widget listen to changes onT
context.read<T>()
, which returnsT
without listening to itcontext.select<T, R>(R cb(T value))
, which allows a widget to listen to only a small part ofT
.
Here
<T>
is a type of value, likeString
,int
,double
or any other complex type We can also use thestatic
methodProvider.of<T>(context)
orProvider.of<T>(context, listen : false)
which will behave similarly towatch
/read
respectively
How these methods work ๐ค
These methods will look up in the widget tree starting from the widget associated with the BuildContext
, and will return the nearest variable of type T
found (or throw if nothing is found).
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
context.watch<String>(),
);
}
}
we must pass the type<>
here in watch()
, searches the same type of data in the widget tree. this watch()
will listen to a state change, and rebuilds with the new value, whenever the state changes.
Similarly, read<T>()
will look for the variable of type T
in the widget tree from it's BuildContext
and retrieves the data.
In the case of select((T value))
, it only listens to those parts of T
, if state change happens in this part, only then it's dependents will update there value. It prevents widget from unnecessarily rebuilding if something other changes.
Now, one static method which works the same as the above method Provider.of<T>(context)
this method performs the same as watch()
, listen to changes in state T
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
String provider = Provider.of<String>(context);
return Text(provider
);
}
}
Provider.of<T>(context)
method has listen
, which is set to true
by default, which means if we are using this method it will listen to state change.
If we set listen
to false
then, this will not listen to state change
Alternatively, the same thing we can also obtain by using Consumer and Selector
Introduction to Consumer and Selector
The consumer gets the value from ancestors using Provider<T>
and passes this value to it's builder
Consumer widget gets the value from the
Provider<T>
when there is noBuildContext
Selector
works same asselect((T value))
, it rebuilds only when, if there is a change in selected value, for which this is listing, no rebuilds happen when others value changes
Selector
determines, if builder
needs to be called again by comparing the previous and new result of selector
using DeepCollectionEquality
from the package collection
Takeaways
- We learned how
provider
helps to overcome the limitations ofsetState
in a complex project - Discussed 5 different types of
provider
- We got to know that
flutter_bloc
&Mobx
also usesprovider
internally - Got introduction about Consumer & Selector Widget
So, that's all for this article. I hope this helped you to understand the provider in Flutter. If I'm left with something crucial, please let me know in the comment section.
Feel free to connect with me on twitter