What's new in provider 4.0.0!
A compilation of what's new in provider 4.0.0, how to migrate, and how to use it
A new milestone has been reached for provider
: 4.0.0!
Let’s go through all the new things together.
Index
- the parameters
builder
/initialBuilder
are removed - providers are now lazy-loaded
- devtool integration
- Selector update
Say bye to builder
and initialBuilder
!
After being deprecated in the version 3.2.0, the parameters builder
and initialBuilder
or providers are now definitely removed.
They are replaced by the create
and update
parameters.
This makes the code a lot more readable and frees the name “builder” for a future
feature.
How to migrate
Classical providers
For classical providers, replace builder
by create
like so.
Before:
Provider(
builder: (context) => MyModel(),
child: SomeWidget(),
)
After:
Provider(
create: (context) => MyModel(),
child: SomeWidget(),
)
“Proxy” providers:
For proxy-providers, replace builder
by update
and initialBuilder
by create
.
Before:
ChangeNotifierProxyProvider<Service, Notifier>(
initialBuilder: (context) => Notifier(),
builder: (context, service, notifier) => notifier..service = service,
child: SomeWidget(),
)
After:
ChangeNotifierProxyProvider<Service, Notifier>(
create: (context) => Notifier(),
update: (context, service, notifier) => notifier..service = service,
child: SomeWidget(),
)
Lazy loading
Until now, inserting any kind of provider in the widget tree immediatly created and subscribed to the object.
This led to situations where an object is created even if it isn’t immediately needed.
Such a situation could be fairly inefficient if the object makes network requests.
Now, as part of the 4.0.0, providers will, by default, lazily create/subscribe to these objects.
What does this mean?
Say you are wrapping your entire application in a StreamProvider like so:
runApp(
StreamProvider(
create: (_) => Connectivity().onConnectivityChanged,
child: MyApp(),
),
);
Until now, even if MyApp
didn’t care about the connectivity status until it reached
a very specific route, your app still listened to the status.
But that is no longer the case.
Now, if you create a provider but don’t use the value immediately, nothing happens.
Note:
The .value
constructor of some providers benefits from lazy-loading too.
The provider will not listen to value
until you try to read the provider.
This can be useful for providers like StreamProvider when combined with Firebase,
as a Firebase Stream cause network requests only if
it has a listener.
As such, it is safe to write:
StreamProvider.value(
value: FirebaseAuth.instance.onAuthStateChanged,
child: MyApp(),
)
I want to purposefully pre-load my value, what can I do?
In some situations, you want to purposefully load the state of provider even if you don’t need it yet.
To do so, you have a few solutions:
-
pass
lazy: false
to a provider:FutureProvider( lazy: false, create: (_) => doSomeHttpRequest(), )
This will revert to the old behavior where the resources are created immediately when the provider is inserted in the widget tree.
-
read the value.
If you want to start the loading of a resource at a specific time, simply accessing it is enough.
For example in the following code,MyModel
starts loading when clicking on the button.FutureProvider<MyModel>( create: (_) => ..., child: Builder( builder: (context) { return RaisedButton( // force the computing of `MyModel` on click onPressed: () => context.read<MyModel>(), child: Text('start loading'), ); }, ), )
Devtool integration
It is now possible to inspect the state of your providers, by using the Flutter devtool.
For example, if you write:
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
Provider(create: (_) => Person(name: 'Remi', age: 24)),
],
child: MyApp(),
)
Then when you’ll open the devtool, you will see:
Note:
If the provider is lazy-loaded and hasn’t been used yet, then the devtool
will instead show <not yet loaded>
.
My devtool says “Instance of MyClass”, why?
If you want an object to be nicely readable inside the devtool, your object will need one of these two things:
-
have a custom
toString
implementation.
This is the easiest solution, and depending on how the class is implemented (such as when using built_value), it may be done automatically for you.class Person { Person({this.name}); final String name; @override String toString() { return '$runtimeType(name: $name)'; } }
-
Make your object mix-in DiagnosticableTreeMixin, and then override debugFillProperties.
More complex but more effective.
Using this approach instead oftoString
gives you the ability to “expand” objects inside the devtool.The easiest solution to implement this approach is to use the refactoring tool of your IDE (works with both VS Code and Android Studio):
Selector update
The Selector widget got a few improvements.
Deep collection comparison
First, the result of selector
will now be deeply compared for collections.
You can therefore write:
Selector<MyModel, List<Person>>(
selector: (_, model) => model.persons.where((p) => p.age > 18).toList(),
builder: (_, adults, __) {
...
}
)
Before, with such code, Selector would rebuild more often than expected, because
selector
created a new list instance all the time and therefore ==
would
return false
.
But now, Selector will understand that the selected value is a collection and cause a rebuild only if their content changed instead.
This applies to List
but also Map
, Set
, and Iterable
.
Custom comparison
Secondly, you can now override the comparison.
Maybe deeply comparing collections is too expensive for your use-case, or you
need a very specific behavior that doesn’t rely on ==
.
For example, if you want to revert to the previous behavior where it didn’t deeply compared collections, you could write:
Selector<MyModel, List<Person>>(
shouldRebuild: (previous, next) => previous == next,
selector: (_, model) => model.persons,
builder: (_, myItem, __) {
...
}
)
Tip:
If you find yourself needing a specific shouldRebuild
or selector
often,
consider subclassing Selector.
That’s it!
I hope you like these changes.
Of course, if you have any suggestion, feel free to raise an issue on https://github.com/rrousselGit/provider.
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
Email