Scoped DI
In this library, scoped dependency injection (DI) refers to injecting providers that are confined to a specific scope.
To make things clearer in this section, let’s take a look at two of the providers from the previous page.
final numberProvider = Provider((context) => 5);
final doubleNumberPlusArgProvider = Provider.withArgument((context, int arg) { final number = numberProvider.of(context); return number * 2 + arg;});
How to scope
Scoping means that the provider must be specified within a ProviderScope
before it can be injected.
In case the provider does not take an argument, we scope it the following way:
ProviderScope( providers: [numberProvider] child: // ...)
In case the provider takes an argument, we need to specify it when providing it.
ProviderScope( providers: [doubleNumberPlusArgProvider(10)] child: // ...)
How to inject
Injecting is the act of retrieving a dependency. It is done with the methods of(context)
and maybeOf(context)
, the latter one being safer because it returns null instead of throwing if the provider is not found in any scopes.
In full example below, we are going to inject the two providers above with numberProvider.of(context)
and doubleNumberPlusArgProvider.of(context)
.
Full example
Try and guess what the displayed text will be before reading the solution.
runApp( MaterialApp( home: Scaffold( body: ProviderScope( providers: [numberProvider], child: ProviderScope( providers: [doubleNumberPlusArgProvider(10)], child: Builder( builder: (context) { final number = numberProvider.of(context); final doubleNumberPlusArg = doubleNumberPlusArgProvider.of(context); return Text('$number $doubleNumberPlusArg'); }, ), ), ), ), ),);
The solution is “5 20”.
Scoping correctly with context
Some providers might have a dependency on other providers. It is important that the other providers are provided in ProviderScope
s that are ancestors to the ProviderScope
providing the dependent provider.
Wrong example
The scope containing doubleNumberPlusArgProvider
needs to be a descendant of the one containing numberProvider
.
This is because doubleNumberPlusArgProvider
uses the context to find the value of numberProvider
. The following will thus not work:
// bad exampleProviderScope( providers: [ numberProvider, doubleNumberPlusArgProvider(10), ], child: // ...)
Placing the ProviderScope
containing doubleNumberPlusArgProvider
above the one containing numberProvider
would also not work. It needs to be like in the full example above.
Graphical representation
When you inject a provider, you need to ensure that one of the ancestors of the widget — where the injection takes place — is a ProviderScope
providing that provider. If this is not the case, an error will be thrown at runtime.
Refer to the graph below to understand the problem.
If you lift the provider scope up to the parent of the widget, the injection will work as expected.