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.
Obtaining a "ref" object
First and foremost, before reading a provider, we need to obtain a "ref" object.
This object is what allows us to interact with providers, be it from a widget or another provider.
Obtaining a "ref" from a provider
All providers receive a "ref" as parameter:
final provider = Provider((ref) {
// use ref to obtain other providers
final repository = ref.watch(repositoryProvider);
return SomeValue(repository);
})
This parameter is safe to pass to the value exposed by the provider.
For example, a common use-case is to pass the provider's "ref" to a StateNotifier:
final counter = StateNotifierProvider<Counter, int>((ref) {
return Counter(ref);
});
class Counter extends StateNotifier<int> {
Counter(this.ref): super(0);
final Ref ref;
void increment() {
// Counter can use the "ref" to read other providers
final repository = ref.read(repositoryProvider);
repository.post('...');
}
}
Doing so would allow our Counter class to read providers.
Obtaining a "ref" from a widget
Widgets naturally do not have a ref parameter. But Riverpod offers multiple solutions to obtain one from widgets.
warning
When a widget obtains a "ref", this "ref" should not be passed around. It should be used only by the widget that created this object.
Extending ConsumerWidget instead of StatelessWidget
The most common solution will be to replace StatelessWidget by ConsumerWidget when creating a widget.
ConsumerWidget is basically identical to StatelessWidget, with the only difference being that it has an extra parameter on its build method: the "ref" object.
A typical ConsumerWidget would look like:
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// use ref to listen to a provider
final counter = ref.watch(counterProvider);
return Text('$counter');
}
}
Extending ConsumerStatefulWidget+ConsumerState instead of StatefulWidget+State
Similar to ConsumerWidget, ConsumerStatefulWidget and ConsumerState are the equivalent of a [StatefulWidget] with its State, with the difference that the state as a "ref" object.
This time, the "ref" isn't passed as parameter of the build method, but is rather a property of the ConsumerState object:
class HomeView extends ConsumerStatefulWidget {
const HomeView({Key? key}): super(key: key);
@override
HomeViewState createState() => HomeViewState();
}
class HomeViewState extends ConsumerState<HomeView> {
@override
void initState() {
super.initState();
// "ref" can be used in all life-cycles of a StatefulWidget.
ref.read(counterProvider);
}
@override
Widget build(BuildContext context) {
// We can also use "ref" to listen to a provider inside the build method
final counter = ref.watch(counterProvider);
return Text('$counter');
}
}
Extending HookConsumerWidget instead of HookWidget
This solution is specific to flutter_hooks users. Since flutter_hooks requires extending HookWidget to work, widgets that use hooks are unable to extend ConsumerWidget.
One solution is, through the package hooks_riverpod, replace HookWidget
by HookConsumerWidget.
HookConsumerWidget acts as both a ConsumerWidget and a HookWidget. This
allows a widget to both listen to providers and use hooks.
An example would be:
class HomeView extends HookConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// HookConsumerWidget allows using hooks inside the build method
final state = useState(0);
// We can also use the ref parameter to listen to providers.
final counter = ref.watch(counterProvider);
return Text('$counter');
}
}
Consumer and HookConsumer widgets
A final solution for obtaining a "ref" inside widgets is to rely on Consumer/ HookConsumer.
These classes are widgets that can be used to obtain a "ref", with the same properties as ConsumerWidget/HookConsumerWidget.
These widgets can be a way to obtain a "ref" without having to define a class. An example would be:
Scaffold(
body: HookConsumer(
builder: (context, ref, child) {
// Like HookConsumerWidget, we can use hooks inside the builder
final state = useState(0);
// We can also use the ref parameter to listen to providers.
final counter = ref.watch(counterProvider);
return Text('$counter');
},
),
)
Using ref to interact with providers
Now that we have a "ref", we can start using it.
There are three primary usages for "ref":
- obtaining the value of a provider and listening to changes, such that when
this value changes, this will rebuild the widget or provider that subscribed
to the value.
That can be done using
ref.watch - adding a listener on a provider, to execute an action whenever
that provider changes.
That can be done usingref.listen. - obtaining the value of a provider while ignoring changes.
This is useful when we need the value of a provider in an event
such as "on click".
That can be done using
ref.read.
note
Whenever possible, prefer using ref.watch over ref.read or ref.listen to
implement a feature.
By changing your implementation to rely on ref.watch, it becomes both reactive
and declarative, which makes your application more maintainable.
Using ref.watch to observe a provider
It is possible to use ref.watch inside the build method of a widget or
inside the body of a provider to have the widget/provider listen to provider:
For example, a provider could use ref.watch to combine multiple providers
into a new value.
An example would be filtering a todo-list. We could have two providers:
filterTypeProvider, a provider that exposes the current type of filter (none, show only completed tasks, ...)todosProvider, a provider that exposes the entire list of tasks
With this, using ref.watch, we could make a third provider that combines both providers to
create a filtered list of tasks:
final filterTypeProvider = StateProvider<FilterType>((ref) => FilterType.none);
final todosProvider = StateNotifierProvider<TodoList, List<Todo>>((ref) => TodoList());
final filteredTodoListProvider = Provider((ref) {
// obtains both the filter and the list of todos
final FilterType filter = ref.watch(filterTypeProvider).state;
final List<Todo> todos = ref.watch(todosProvider);
switch (filter) {
case FilterType.completed:
// return the completed list of todos
return todos.where((todo) => todo.isCompleted).toList();
case FilterType.none:
// returns the unfiltered list of todos
return todos;
}
});
With this code, filteredTodoListProvider now exposes the filtered list of tasks.
The filtered list will also automatically update if the filter or the list of tasks changed. Yet at the same time, the filtered list will not be recomputed if neither the filter nor the list of tasks changed.
Similarly, a widget could use ref.watch to show a user interface that depends
on a provider:
final counterProvider = StateProvider((ref) => 0);
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// use ref to listen to a provider
final counter = ref.watch(counterProvider).state;
return Text('$counter');
}
}
This snippet shows a widgets that listens to a provider which stores a counter. And if that counter changes, the widget will rebuild and the UI will update to show the new value.
caution
The watch method should not be called asynchronously,
like inside onPressed or a ElevatedButton. Not should it be used
inside initState and other State life-cycles.
In those cases, consider using ref.read instead.
Using ref.listen to react to a provider change
Similarly to ref.watch, it is possible to use ref.listen to observe a provider.
The main difference between them is that, rather than rebuilding the widget/provider
if the listened provider changes, using ref.listen will instead call a custom function.
That can be useful to perform actions when a certain change happens, such that to show a snackbar when an error happens.
The ref.listen method can be used inside body of a provider:
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
final anotherProvider = Provider((ref) {
ref.listen<int>(counterProvider, (int count) {
print('The counter changed ${count}');
});
...
});
or inside the build method of a widget:
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<int>(counterProvider, (int count) {
print('The counter changed ${count}');
});
...
}
}
caution
The listen method should not be called asynchronously,
like inside onPressed or an ElevatedButton. Nor should it be used
inside initState and other State life-cycles.
Using ref.read to obtain the state of a provider once
The ref.read method is a way to obtain the state of a provider, without any
extra effect.
It is commonly used inside functions triggered on user interactions.
For example, we can use ref.read to increment a counter when a user click on a button:
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
class HomeView extends ConsumerWidget {
const HomeView({Key? key}): super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
// Call `increment()` on the `Counter` class
ref.read(counterProvider.notifier).increment();
},
),
);
}
}
note
Using ref.read should be avoided as much as possible.
It exists as a work-around for cases where using watch or listen would
be otherwise too inconvenient to use.
If you can, it is almost always better to use watch/listen, especially watch.
DON'T use ref.read inside the build method
You might be tempted to use ref.read to optimize the performance of a widget
by doing:
final counterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
// use "read" to ignore updates on a provider
final counter = ref.read(counterProvider);
return ElevatedButton(
onPressed: () => counter.state++,
)
}
But this is a very bad practice and can cause bugs that are difficult to track.
Using ref.read this way is commonly associated with the thought "The value
exposed by a provider never changes so using 'ref.read' is safe".
The problem with this assumption is that, while today that provider
may indeed never update its value, there is no guarantee that tomorrow will be
the same.
Softwares tend to change a lot, and it is likely that in the future, a value
that previously never changed will need to change.
But if you used ref.read, when that value starts to change, you would have
to go through your entire codebase to change ref.read into ref.watch β
which is error prone and chances are you'll forget some cases.
Whereas if you used ref.watch to begin with, you wouldn't have any problem
But I wanted to use ref.read to reduce the number of times my widget rebuild
While the goal is commendable, it is important to note that you can achieve the
exact same effect (reducing the number of builds) using ref.watch instead.
Providers offers various way to obtain a value while reducing the number of rebuilds, which you could use instead.
For example instead of
final counterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
StateController<int> counter = ref.read(counterProvider);
return ElevatedButton(
onPressed: () => counter.state++,
)
}
we could do:
final counterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
// Subscribes to the StateController instance but do not rebuild the
// widget if its state changes
StateController<int> counter = ref.watch(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.state++,
)
}
Both snippets achieve the same effect: our button will not rebuild when the counter increments.
On the other hand, the second approach supports cases where the counter is reset.
For example, another part of the application could call:
ref.refresh(counterProvider);
which would recreate the StateController object.
If we used ref.read here, our button would still use the previous
StateController instance, which was disposed and should no-longer be used.
Whereas using ref.watch correctly rebuilt the button to use the new StateController.
Deciding what to read
Depending on the provider you want to listen, you may have multiple possible values that you can listen.
As example, consider the following StreamProvider:
final userProvider = StreamProvider<User>(...);
When reading this userProvider, you can:
synchronously read the current state by listening to
userProvideritself:Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<User> user = ref.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, WidgetRef ref) {
Stream<User> user = ref.watch(userProvider.stream);
}obtain a Future that resolves with the latest value emitted, by listening to
userProvider.future:Widget build(BuildContext context, WidgetRef ref) {
Future<User> user = ref.watch(userProvider.future);
}
Other providers may offer different alternative values.
For more information, refer to the documentation of each provider by
consulting the API reference.
Using "select" to filter rebuilds
One final feature to mention related to reading providers is the ability to
reduce the number of times a widget/provider rebuilds, or how often ref.listen
executes a function.
This is important to keep in mind as, by default, listening to a provider listens to the entire object. But in some cases, a widget/provider may only care about some properties instead of the whole object.
For example, a provider may expose a User:
abstract class User {
String get name;
int get age;
}
But a widget may only use the user name:
Widget build(BuildContext context, WidgetRef ref) {
User user = ref.watch(userProvider);
return Text(user.name);
}
If we naively used ref.watch, this would rebuild the widget when the user's
age changes.
The solution is to use select to explicitly tell Riverpod that we only
want to listen to some properties of the User.
The updated code would be:
Widget build(BuildContext context, WidgetRef ref) {
String name = ref.watch(userProvider.select((user) => user.name))
return Text(name);
}
The way this works is, by using select, we are specifying a function
that returns the property that we care about.
Then, whenever the User changes, Riverpod will call this function and
compare the previous and new result. If they are different (such as when the name
changed), Riverpod will rebuild the widget.
But if they are equal (such as when the age changed), then Riverpod will not
rebuild the widget.
info
It is possible to use select with ref.listen too:
ref.listen<String>(
userProvider.select((user) => user.name),
(String name) {
print('The user name changed $name');
}
);
Doing so will call the listener only when the name changes.
tip
You don't have to return a property of the object. Any value that overrides == will work. For example you could do:
final label = ref.watch(userProvider.select((user) => 'Mr ${user.name}'));