Skip to content

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, doubleNumberPlusArgProvider],
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, to handle such cases, you can:

  1. Access another provider if it is declared in an ancestor ProviderScope.
  2. Declare the dependent provider after the provider it depends on, within the same ProviderScope. The order of declaration matters here otherwise a ProviderForwardReferenceError will be thrown at runtime.

Wrong example

The provider doubleNumberPlusArgProvider depends on numberProvider. Therefore, when scoping them both in the same ProviderScope, numberProvider must be declared first. Therefore, the following code will lead to a ProviderForwardReferenceError:

Wrong example
ProviderScope(
providers: [
doubleNumberPlusArgProvider(10),
numberProvider,
],
child: // ...
)

The error can be prevented by reordering the list of providers:

Correct example
ProviderScope(
providers: [
numberProvider,
doubleNumberPlusArgProvider(10),
],
child: // ...
)

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.

Graphical representation of provider scope exception

If you lift the provider scope up to the parent of the widget, the injection will work as expected.

Graphical representation of provider scope