Reading a provider

Before reading this guide, make sure to read about Providers first.

In this guide, we will see how to consume a provider.

There are multiple ways to read a provider, which change slightly based on different criterias.
Long story short, here is a decision graph to help you decide what to use to read a provider:

Decision graph for reading providers

Next, we will see each individual case and show how they work.
For this guide, consider the following provider:

final counterProvider = StateProvider((ref) => 0);

Deciding what to read

Depending on the provider you want to listen, you may have multiple possible values that you can listen.

An example, consider the following [StreamProvider]:

final userProvider = StreamProvider<User>(...);

When reading this userProvider, you can:

  • synchronously read the current state by listening to userProvider itself:

    Widget build(BuildContext context, ScopedReader watch) {
    AsyncValue<User> user = watch(userProvider);
    return user.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stack) => const Text('Oops'),
    data: (user) => Text(user.name),
    );
    }
  • obtain the associated [Stream], by listening to userProvider.stream:

    Widget build(BuildContext context, ScopedReader watch) {
    Stream<User> user = watch(userProvider.stream);
    }
  • obtain a [Future] that resolves with the latest value emitted, by listening to userProvider.last:

    Widget build(BuildContext context, ScopedReader watch) {
    Future<User> user = watch(userProvider.last);
    }

For more information, refer to the documentation of each individual provider by consulting the API reference.

Consuming a provider inside widgets

In this section, we will see how Flutter widgets can interact with providers.

ConsumerWidget

ConsumerWidget is a base-class for Widgets similar to StatelessWidget, but that allows you to listen to providers.

class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
// Listens to the value exposed by counterProvider
int count = watch(counterProvider).state;
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Text('$count'),
),
);
}
}

Notice how the value of counterProvider is obtained by a function called watch.
This function is what makes ConsumerWidget listen to our provider and rebuild when the value exposed changed.

caution

The watch method passed as an argument of ConsumerWidget should not be called asynchronously, like inside onPressed or a RaisedButton.

If you need to read a provider in response to a user event, see myProvider.read(BuildContext)

Consumer

Consumer is a ConsumerWidget that you can use to optimize the performances of your application by rebuilding only the widgets that needs use the data.

For example, we could update the code snippet from ConsumerWidget we saw previously to rebuild only the Text when the counter changes:

class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
// Rebuild only the Text when counterProvider updates
builder: (context, watch, child) {
// Listens to the value exposed by counterProvider
int count = watch(counterProvider).state;
return Text('$count');
},
),
),
);
}
}

This example will make our UI listen to counterProvider and rebuild a Text (and only that Text) when the counter changes.

useProvider (hooks_riverpod only)

If you are using flutter_hooks/hooks_riverpod, an alternative to ConsumerWidget is to use useProvider.

class Count extends HookWidget {
@override
Widget build(BuildContext context) {
int count = useProvider(counterProvider).state;
return Text('$count');
}
}

This can be useful when you want to use both flutter_hooks and Riverpod together.
This syntax also supports a feature that are not supported by ConsumerWidget: [select]

class Example extends HookWidget {
@override
Widget build(BuildContext context) {
bool isAbove5 = useProvider(counterProvider.select((c) => s.state > 5));
return Text('Is counter > 5 ? $isAbove5');
}
}

Using this syntax, our widget will rebuild only if the isAbove5 variable changes. Which means that if the counter changes from 1 to 2, this will not cause our widget to rebuild.

context.read(myProvider)

With Consumer and useProvider, we've seen how to listen to a provider.

But in some situations, there's no value in listening to the object. For example, we may need the object only for the onPressed of a RaisedButton.

We could use useProvider/Consumer:

Consumer(builder: (context, watch, _) {
StateController<int> counter = watch(counterProvider);
return RaisedButton(
onPressed: () => counter.state++,
child: Text('increment'),
)
});

But that is not efficient. Depending on the provider listened, this could cause the RaisedButton to rebuild when the counter changes, even when the counter isn't actually used to build the RaisedButton.

That's where context.read(myProvider) is a solution.

Using it, we could refactor our previous code to:

@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => context.read(counterProvider).state++,
child: Text('increment'),
);
}

By doing so, clicking on our button still increments the counter. But we are no-longer listening to the provider, which avoids unnecessary rebuilds.

I don't see the context.read method, why is that?

If you do not see context.read, it is likely that you did not import the correct package. To see this method, you must import either package:flutter_riverpod or package:hooks_riverpod.

info

Depending on the provider that you are listening to, you may not need to do this.
For example, StateNotifierProvider has a built-in way obtain a StateNotifier without listening to its state:

class Counter extends StateNotifier<int> {
Counter(): super(0);
void increment() => state++;
}
final counterProvider = StateNotifierProvider((ref) => Counter());
// ...
@override
Widget build(BuildContext context, ScopedReader watch) {
// Obtains Counter without listening to Counter.state.
// Will not cause the button to rebuild when the counter changes.
final Counter counter = watch(counterProvider);
return RaisedButton(
onPressed: counter.increment,
child: Text('increment'),
);
}
caution

Avoid calling context.read inside the build method of a Widget.
If you want to optimize rebuilds, extract the value listened in a Provider instead.

ProviderListener

In some situations, you may want to your Widget tree to push a route or show a dialog after a change on a provider.

Such behavior would be implemented using the ProviderListener Widget.

Widget build(BuildContext context) {
return ProviderListener<StateController<int>>(
provider: counterProvider,
onChange: (context, counter) {
if (counter.state == 5) {
showDialog(...);
}
},
child: Whatever(),
);
}

This will show a dialog when the counter reaches 5.

Reading a provider inside another provider

A common use-case when making providers is to want to create an object from other objects.
For example, we may want to create a UserController from a UserRepository, where both objects would be exposed in a different provider.

Such scenario is possible by using the ref object that providers receives as parameter:

final userRepositoryProvider = Provider((ref) => UserRepository());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
// Read userRepositoryProvider and create a UserController from the result
repository: ref.watch(userRepositoryProvider),
);
});

Make sure to check out the Combining Providers guide for more informations such as:

  • What happens if I create an object from a value which can change over time?
  • Some good practices tips

Reading a provider outside of providers using Dart only

In some scenarios, you may want to read a provider in a package that has no dependency on Flutter.
A common use-case is to test a class unrelated to widgets.

In this situation, you can use ProviderContainer, which is a low-level utility to manipulate providers.

The following snippet demonstrates how a test can read providers without using Flutter:

test('counter starts at 0', () {
final container = ProviderContainer();
StateController<int> counter = container.read(counterProvider);
expect(counter.state, 0);
});
danger

Do not reuse the instance of ProviderContainer between tests.
This ensures that the state of your providers properly resets between test cases.