Michael Gundlach
Michael Gundlach

Reputation: 109423

Riverpod: Is it wrong to refer in onPressed to a provider obtained earlier by ref.watch?

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.

Upvotes: 1

Views: 955

Answers (3)

Ruble
Ruble

Reputation: 4824

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')),
      ],
    );
  }
}

Upvotes: 1

john
john

Reputation: 1998

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.

Upvotes: 1

My Car
My Car

Reputation: 4566

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.

Upvotes: 0

Related Questions