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],
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 ProviderScopes 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 example
ProviderScope(
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.

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