1

Riverpod docs say to use ref.read in callbacks like a button's onPressed, since you must not use ref.watch there.

Is it OK to do something like this?

build(context, ref) {
  // Use ref.watch ahead of time, because I refer to p all over 
  // the widget tree. p is a mutable ChangeNotifier
  final p = ref.watch(myProvider);

  return ElevatedButton(
    onPressed: () {
      // Refer to provider obtained via ref.watch earlier
      p.mutateSomehow(),
    },
    child: /* huge tree referring to p 20 times */
  );
}

? I'm doing this all over my code and it seems to work fine, but if this pattern were OK it seems like Riverpod would just recommend it and deprecate ref.read.

Michael Gundlach
  • 106,555
  • 11
  • 37
  • 41

3 Answers3

1

let say you have a provider defined like this: final pProvider = StateProvider<int>((ref)=> 0);

and inside your build, you watch its value like this: final p = ref.watch(pProvider.state).state;

now, the p inside your build is a stored value of your provider.

inside your callback, if you want to increment the value of p, you can do so by just saying p++ or p + 1, however, that means that you are just incrementing the fetched value and not the state of the provider itself. this is mostly the scenario when you want to fetch the value of the provider, then add something thing to it before saving it to the database.

On the other hand, if you want to change the state of the provider that is being watched by your ref.watch method, then you have to increment your state provider like this ref.read(pProvider.notifier).state++ this ensures that what your are incrementing is the state of the provider not the stored value. and this also triggers a rebuild on your widget because the state being watched is changing.

Hope this helps explain your concern based on what I understood.

john
  • 1,438
  • 8
  • 18
  • Thanks John. In my case, p is a mutable ChangeNotifier subclass holding my game's state: score counts, player position, etc. The question is whether it is safe to mutate p from onPressed, or whether I must instead mutate ref.read(myProvider). I'm sorry if I'm not clear or if I'm misunderstandung you :) – Michael Gundlach Oct 24 '22 at 04:13
  • Maybe your point is that, in my particular case referring to p would be fine, but when p is immutable I must refer to ref.read(myProvider.notifier) in onPressed rather than to an earlier-stored ref.watch(myProvider.notifier)? – Michael Gundlach Oct 24 '22 at 04:18
  • i dont know how you defined your provider thats why i gave stateProvider as an example. but if that is a changeNotifer, then yes can say `p.doSomthing()` that is perfectly fine. just dont forget to notifyListeners if you want rebuild. – john Oct 24 '22 at 04:23
  • i do this a lot in my project say i have a controller provider, i define it like this `final controller = ref.watch(controllerProvider.notifier);` then to use the method inside, just say `controller.addItem()`. – john Oct 24 '22 at 04:29
1

I think this is done to GUARANTEE that we are getting the most up-to-date status of the provider. Therefore, one should use ref.read before any action in (){}. This way we deprive ourselves of the possibility of making mistakes in the future :)

And here's a full example of a possible situation:

void main() => runApp(const ProviderScope(child: MyApp()));

final indexProvider = StateProvider<int>((ref) {
  ref.listenSelf((previous, next) {
    print(ref.controller.state);
  });
  return 0;
});

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $MyApp');

    return MaterialApp(
      home: Center(
        child: Column(
          children: const [
            SizedBox(height: 50.0),
            ButtonInternalState(),
            SizedBox(height: 50.0),
            ButtonProviderState(),
          ],
        ),
      ),
    );
  }
}

class ButtonInternalState extends ConsumerWidget {
  const ButtonInternalState({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $ButtonInternalState');

    // Once upon a time, all we had to do was read the data once and not keep track of anything
    final StateController<int> indexState = ref.read(indexProvider.state);

    return ElevatedButton(
      onPressed: () => indexState.state++,

      // It would have avoided the mistake
      // onPressed: () => ref.read(indexProvider.state).state++,

      child: Text('The state our wrong provider - ${indexState.state}'),
    );
  }
}

class ButtonProviderState extends ConsumerWidget {
  const ButtonProviderState({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('build $ButtonProviderState');

    final int index = ref.watch(indexProvider);

    return Column(
      children: [
        ElevatedButton(
            onPressed: () => ref.read(indexProvider.notifier).state++,
            child: Text('Here changes state the provider - $index')),
        ElevatedButton(
            onPressed: () => ref.refresh(indexProvider),
            child: const Text('Refresh our provider')),
      ],
    );
  }
}
Ruble
  • 2,589
  • 3
  • 6
  • 29
0

ref.read and ref.watch are different:

We use ref.read to get the provider's value once (one-time read).

We use ref.watch to get the provider's value the first time and every time the value changes (watch it as if you subscribed to the provider, so you'll be notified of changes at any time).

You don't need to always get the value, you should only get the provider's value once.

Update:

I suggest you use p elsewhere and use ref.watch(myProvider).doSomething() in the onPressed function.

My Car
  • 4,198
  • 5
  • 17
  • 50
  • Thanks, but I extracted `p` because I refer to it throughout my complicated Widget tree. I get that if I had just inlined `ref.watch` once in my Widget tree, I could use `ref.read` in `onPressed`. My question is whether, given that I *already* have a `p` lying around, can I just use that reference directly rather than calling `ref.read`. I have updated my question to clarify. – Michael Gundlach Oct 24 '22 at 02:44
  • Hi @MichaelGundlach, I suggest you use `p` elsewhere and use `ref.watch(myProvider).doSomething()` in the `onPressed` function – My Car Oct 24 '22 at 02:49
  • Sorry, from https://riverpod.dev/docs/concepts/reading/: "The watch method should not be called asynchronously, like inside an onPressed of an ElevatedButton." – Michael Gundlach Oct 24 '22 at 02:56
  • Sorry, it's `ref.read(myProvider).doSomething()` – My Car Oct 24 '22 at 02:57
  • I suggest you use `p` elsewhere and use `ref.read(myProvider).doSomething()` in the `onPressed` function – My Car Oct 24 '22 at 02:58
  • Thank you for the suggestion, but if there a technical reason why using p is any different? – Michael Gundlach Oct 24 '22 at 03:05
  • Do you need to always get the value when using `p`? If yes, use `p` as you need to use `ref.watch(myProvider)` – My Car Oct 24 '22 at 03:10