1

The Riverpod documentation clearly states that:

The watch method should not be called asynchronously, like inside an onPressed of an ElevatedButton. Nor should it be used inside initState and other State life-cycles.
In those cases, consider using ref.read instead.

You might be tempted to use ref.read to optimize the performance of a widget by doing: #
For example instead of:

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state = state + 1;
}

Widget build(BuildContext context, WidgetRef ref) {
  // use "read" to ignore updates on a provider
  final counter = ref.read(counterProvider.notifier);
  return ElevatedButton(
    onPressed: counter.increment,
    child: const Text('button'),
  );
}

we could do:

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state = state + 1;
}

Widget build(BuildContext context, WidgetRef ref) {
  Counter counter = ref.watch(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.increment(),
    child: const Text('button'),
  );
}

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.

The documentation confuses me with using or not using ref.watch() inside onPressed.

There are a few more things I needed answers for:

  • The docs advise avoiding read method inside build method means immediately inside build method before return statement, or everywhere?
  • If watch method must not be used inside onPressed but helps in cases when providers are reset, how and when should I use them as onPressed:() => ref.read(myProvider.notifier).myMethod() vs ref.watch(myProvider.notifier).myMethod()
  • Is the advice to avoid watch method for async calls like in onPressed is for both providers ref.watch(myProvider) and notifiers ref.watch(myProvider.notifier) or any one?
  • As of my limited knowledge, watching notifiers do not cause rebuilds and must be safe to use anywhere for calling provider methods. Do I know right?
  • What difference does it make to access provider methods from inside functions like onPressed: () => ref.watch|read(myProvider.notifier).myMethod() i.e every time accessing through ref, or using a variable like in above code examples:
// inside build
var notif = ref.read|watch(myProvider.notifier);
// somewhere inside return statement in a button's onPressed:
{
    notif.myMethod();
}

Which approach must be preferred in which cases? And if latter method is more prone to unexpected outcomes based on the differences, How?

Johny Gates
  • 75
  • 1
  • 9
  • Don't watch a notifier. It's boring. It never emits. You read a notifier (to call mutate methods) and watch a value (to know when it changes). – Randal Schwartz Aug 19 '23 at 08:58
  • According to docs, using `read` for notifiers has a disadvantage of not reacting to case when provider is refreshed, and notifier is recreated. Also they have mentioned the code storing reference of notifier through watch and using it in onPressed (also mentd. in my question). Contrary to which they advise to use `read` inside such callback functions, which confuses me. @RandalSchwartz – Johny Gates Aug 19 '23 at 09:33

1 Answers1

1
  • Avoiding read method inside the build method: This refers to avoiding calling ref.read directly inside the build method before the return statement. Using ref.read elsewhere, like in other lifecycle methods, is generally okay, or inside an onpressed.

  • watch method inside onPressed: If you use ref.watch inside the onPressed callback, you are watching the provider every time the button is pressed. It could lead to unexpected behavior, especially if the provider changes outside of the build method. Instead, you should use ref.read inside onPressed since you're merely reading the provider's value, not watching for changes.

  • Avoiding watch for async calls: This advice generally applies to both providers (ref.watch(myProvider)) and notifiers (ref.watch(myProvider.notifier)). You typically use watch within the build method to rebuild the widget when the provider changes, not in callbacks like onPressed. Or inside other providers.

  • Watching notifiers: You are correct that watching notifiers themselves (e.g., ref.watch(myProvider.notifier)) doesn't necessarily cause rebuilds, as it depends on how the provider is structured. However, the general advice to avoid using watch in callbacks like onPressed still applies, as it can lead to unexpected behavior.

  • What difference does it make? Please check this answer from Rémi Rousselet

I'd like to emphasize that the architecture of your app should govern the correct usage of your providers. It might be beneficial to categorize your UI relates providers into two distinct groups: Controller providers and State-holding providers.

Controller Providers: These providers would primarily involve calling void methods, reading other providers, triggering state changes, and managing navigation. Since these providers would merely consist of a class with methods, there's no need to watch for changes from them. They would be implemented using simple Providers.

State-holding Providers: These providers can be monitored for changes, and they play a crucial role in maintaining and reflecting the state within your application. Before you begin building, I would advise exploring some potential architectures to ensure you're on the right track. Here's a helpful discussion that can guide you: Riverpod Discussion on Architecture. By dividing your providers in this manner, you can cultivate a more coherent and effective design for your application.

Qwadrox
  • 611
  • 1
  • 6
  • 10
  • Still I feel bit unanswered for if no changes are to be watched then `read` must be used but if so, why does official docs suggest using reference of notifier, received using `watch` and saying that doing so would take care of provider refreshes, which `read` can't do. If `watch` is of so much advantage, why is it suggested to use `read` to trigger state changes inside onPressed? This is my whole point of confusion. – Johny Gates Aug 19 '23 at 10:00
  • Why use ref.read inside onPressed? Inside an onPressed callback, you typically want to perform an action based on the current state of a provider, such as triggering a state change. You don't want the widget itself to rebuild as a result of this action. By using ref.read, you can access the current value of the provider without setting up a watch relationship. This lets you perform the action without causing unnecessary rebuilds of the widget. – Qwadrox Aug 19 '23 at 13:51
  • You are correct & docs also suggest the same but contrary to it, they show example using `ref.watch` (also mentd. in my question). Also I don't see unnecessary rebuilds as I am watching 'notifiers' not providers. Still I would prefer `read` but I really hope to understand the specifics of why an otherwise approach has been shown as a good practice in docs? Is it because a reference is created for notifiers to be used by multiple widgets in example or some other reason @Qwadrox – Johny Gates Aug 19 '23 at 15:10