provider 3.1.0 provider: ^3.1.0 copied to clipboard
A mixture between dependency injection and state management, built with widgets for widgets.
A mixture between dependency injection (DI) and state management, built with widgets for widgets.
It purposefully uses widgets for DI/state management instead of dart-only
classes like Stream
.
The reason is, widgets are very simple yet robust and scalable.
By using widgets for state management, provider
can guarantee:
- maintainability, through a forced uni-directional data-flow
- testability/composability, since it is always possible to mock/override a value
- robustness, as it is harder to forget to handle the update scenario of a model/widget
To read more about provider
, see the documentation.
Migration from v2.0.0 to v3.0.0 #
- Providers can no longer be instantiated with
const
. Provider
now throws if used with aListenable
/Stream
. Consider usingListenableProvider
/StreamProvider
instead. Alternatively, this exception can be disabled by settingProvider.debugCheckInvalidValueType
tonull
like so:
void main() {
Provider.debugCheckInvalidValueType = null;
runApp(MyApp());
}
- All
XXProvider.value
constructors now usevalue
as parameter name.
Before:
ChangeNotifierProvider.value(notifier: myNotifier),
After:
ChangeNotifierProvider.value(value: myNotifier),
StreamProvider
's default constructor now builds aStream
instead of aStreamController
. The previous behavior has been moved to the named constructorStreamProvider.controller
.
Before:
StreamProvider(builder: (_) => StreamController<int>()),
After:
StreamProvider.controller(builder: (_) => StreamController<int>()),
Usage #
Exposing a value #
To expose a variable using provider
, wrap any widget into one of the provider
widgets from this package and pass it your variable. Then, all descendants of
the newly added provider widget can access this variable.
A simple example would be to wrap the entire application into a Provider
widget and pass it our variable:
Provider<String>.value(
value: 'Hello World',
child: MaterialApp(
home: Home(),
)
)
Alternatively, for complex objects, most providers expose a constructor that
takes a function to create the value. The provider will call that function only
once, when inserting the widget in the tree, and expose the result. This is
perfect for exposing a complex object that never changes over time without
writing a StatefulWidget
.
The following creates and exposes a MyComplexClass
. And in the event where
Provider
is removed from the widget tree, the instantiated MyComplexClass
will be disposed.
Provider<MyComplexClass>(
builder: (context) => MyComplexClass(),
dispose: (context, value) => value.dispose()
child: SomeWidget(),
)
Reading a value #
The easiest way to read a value is by using the static method
Provider.of<T>(BuildContext context)
.
This method will look up in the widget tree starting from the widget associated
with the BuildContext
passed and it will return the nearest variable of type
T
found (or throw if nothing is found).
Combined with the first example of exposing a value, this
widget will read the exposed String
and render "Hello World."
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
Provider.of<String>(context)
);
}
}
Alternatively instead of using Provider.of
, we can use Consumer
and Selector
.
These can be useful for performance optimizations or when it is difficult to
obtain a BuildContext
descendant of the provider.
See the FAQ or the documentation of Consumer and Selector for more information.
MultiProvider #
When injecting many values in big applications, Provider
can rapidly become
pretty nested:
Provider<Foo>.value(
value: foo,
child: Provider<Bar>.value(
value: bar,
child: Provider<Baz>.value(
value: baz,
child: someWidget,
)
)
)
In that situation, we can use MultiProvider
to improve the readability:
MultiProvider(
providers: [
Provider<Foo>.value(value: foo),
Provider<Bar>.value(value: bar),
Provider<Baz>.value(value: baz),
],
child: someWidget,
)
The behavior of both examples is strictly the same. MultiProvider
only changes
the appearance of the code.
ProxyProvider #
Since the 3.0.0, there is a new kind of provider: ProxyProvider
.
ProxyProvider
is a provider that combines multiple values from other providers
into a new object, and sends the result to Provider
.
That new object will then be updated whenever one of the providers it depends on updates.
The following example uses ProxyProvider
to build translations based on a
counter coming from another provider.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Counter()),
ProxyProvider<Counter, Translations>(
builder: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
It comes under multiple variations, such as:
-
ProxyProvider
vsProxyProvider2
vsProxyProvider3
, ...That digit after the class name is the number of other providers that
ProxyProvider
depends on. -
ProxyProvider
vsChangeNotifierProxyProvider
vsListenableProxyProvider
, ...They all work similarly, but instead of sending the result into a
Provider
, aChangeNotifierProxyProvider
will send its value to aChangeNotifierProvider
.
FAQ #
My widget rebuilds too often, what can I do?
Instead of Provider.of
, you can use Consumer
/Selector
.
Their optional child
argument allows to rebuild only a very specific part of
the widget tree:
Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
In this example, only Bar
will rebuild when A
updates. Foo
and Baz
won't
unnecesseraly rebuild.
To go one step further, it is possible to use Selector
to ignore changes if
they don't have an impact on the widget-tree:
Selector<List, int>(
selector: (_, list) => list.length,
builder: (_, length, __) {
return Text('$length');
}
);
This snippet will rebuild only if the length of the list changes. But it won't unnecessarily update if an item is updated.
Can I obtain two different providers using the same type?
No. While you can have multiple providers sharing the same type, a widget will be able to obtain only one of them: the closest ancestor.
Instead, you must explicitly give both providers a different type.
Instead of:
Provider<String>(
builder: (_) => 'England',
child: Provider<Sring>(
builder: (_) => 'London',
child: ...,
),
),
Prefer:
Provider<Country>(
builder: (_) => Country('England'),
child: Provider<City>(
builder: (_) => City('London'),
child: ...,
),
),
Existing providers #
provider
exposes a few different kinds of "provider" for different types of objects.
The complete list of all the objects availables is here
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. |