A dependency injection system built with widgets for widgets. provider
is mostly syntax sugar for InheritedWidget
,
to make common use-cases straightforward.
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(),
)
The easiest way to read a value is by using the static method Provider.of<T>(BuildContext context)
. This method will look
up in 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 if 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 the Consumer
widget.
This can be useful for performance optimizations or when it is difficult to obtain a BuildContext
descendant of the provider.
Provider<String>.value(
value: 'Hello World',
child: Consumer<String>(
builder: (context, value, child) => Text(value),
),
);
Note that you can freely use multiple providers with different types together:
Provider<int>.value(
value: 42,
child: Provider<String>.value(
value: 'Hello World',
child: // ...
)
)
And obtain their value independently:
var value = Provider.of<int>(context);
var value2 = Provider.of<String>(context);
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.
provider
exposes a few different kinds of "provider" for different types of 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 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. |