Skip to content

Providers

A provider is a tool that helps manage and inject dependencies in an application, making it easier to share data or services across different parts of the app.

Providers

Declare a new provider either as a global final variable or a final static field.

Example with a global final variable:

/// global scope
final numberProvider = Provider((context) => 5);

If there is only a provider per class, you can also create a final static field. This comes down to personal preference.

class MyDatabase {
static final provider = Provider((context) => MyDatabase());
}

Injection of other providers with context

Providers can leverage the context to inject other providers. The context will be relative to the scope in which they are provided.

final doubleNumberProvider = Provider((context) {
final number = numberProvider.of(context);
return number * 2;
});

Providers with argument

Providers need to be provided before they can be injected in the widget tree. Sometimes, they need an initial argument so that they can be instantiated correctly. This is possible with Provider.withArgument.

final numberPlusArgProvider = Provider.withArgument((context, int arg) {
return 5 + arg;
});

An example where this might make more sense would be an application with multi-account support, where the database is loaded per user, and the filepath of the database contains the user ID:

class MyDatabase {
static final provider = Provider.withArgument((context, String userId) => MyDatabase.fromId(id));
}

This MyDatabase.provider has to be provided in a subtree (of the widget tree) belonging to the currently logged user.

Injection of other providers with context

Providers can both take an argument and rely on context.

final doubleNumberPlusArgProvider = Provider.withArgument((context, int arg) {
final number = numberProvider.of(context);
return number * 2 + arg;
});

Lazy creation of the values

The value of a provider is always created lazily: the create function is called the first time the provider is injected, and never before. If a provider is never injected, its value is never created.

Creating a value as soon as a scope is mounted

Sometimes a value has to exist even if no widget injects it yet, e.g. because it starts a subscription or warms up a cache. In that case, inject it in a widget placed below the scope providing it:

ProviderScope(
providers: [engineProvider(), userProvider(42)],
child: Builder(
builder: (context) {
engineProvider.of(context); // created here
userProvider.of(context); // works for argument providers too
return const MyPage();
},
),
)

The Builder is what makes this work: its context is a descendant of the ProviderScope, therefore the injection finds it. The value is created only once, even though the builder may run again.

If you prefer the injection to happen exactly once per mount, do it in the initState of a widget placed below the scope. This is safe, since of(context) does not make the widget depend on the provider:

class EagerProviders extends StatefulWidget {
const EagerProviders({required this.child, super.key});
final Widget child;
@override
State<EagerProviders> createState() => _EagerProvidersState();
}
class _EagerProvidersState extends State<EagerProviders> {
@override
void initState() {
super.initState();
engineProvider.of(context);
}
@override
Widget build(BuildContext context) => widget.child;
}

Optional parameters

When defining a provider, we need to pass the positional create argument, which is a function used to generate the value contained by the provider.

There are also two optional named parameters that can be specified.

ParameterDefaultDescription
disposenullThe function to call when the scope containing the provider gets disposed. It is used to dispose correctly the value held by the provider.
debugNamenullAn optional name, shown in the error messages of this library, which makes a provider easier to recognize.

Overriding a provider

A provider can be replaced by another provider of the same type through overrideWith, which is meant to be used for testing:

final myProviderOverride = myProvider.overrideWith(
Provider((context) => MyMock()),
);

The same works for providers with an argument, as long as the mock takes an argument of the same type:

final myArgProviderOverride = myArgProvider.overrideWith(
Provider.withArgument((context, int arg) => MyMock(arg)),
);

Since the override is a provider itself, it also controls how the mocked value is created and disposed. Refer to the Testing page to see how overrides are inserted into the widget tree.