Albert Hattingh
Albert Hattingh

Reputation: 118

After Riverpod state update on one widget, ref.watch doesn't trigger on my other widget

I'm busy implementing a simple counter using riverpod. From the examples I've seen, the riverpod state is updated on one widget (by doing something like ref.read(counterProvider.notifier).increment()) and changes to state are watched on the same widget. When I try this it works as expected, using ref.watch(counterProvider) in the build method of the same widget. The counter is updated on the UI.

My problem comes in when I update the counter on one widget, and watch for changes to the counter state on another widget (these widgets are two different tabs in a TabView). So basically my increment button is on one tab, and the value of the counter is displayed on the next tab. In this scenario, the counter doesn't get updated.

The weird thing is, if I add a ref.watch on the same widget where I do the state update, the ref.watch on the second tab also starts working and updates the UI correctly on that tab.

Anyone know why this might be happening? Both of these widgets are ConsumerStatefulWidgets and my ProviderScope is at the root of my app. I'm using riverpod V2, with the code generation.

My provider:

part 'counter.provider.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() {
    return 2;
  }

  void increment() {
    state++;
  }
}

First widget (state update):

ref.read(counterProvider.notifier).increment();

Second widget (state reading):

var x = ref.watch(counterProvider);

Upvotes: 0

Views: 3104

Answers (2)

Diomoid
Diomoid

Reputation: 113

Since this is the first search result for "riverpod ref.watch not working", I thought I would share this tricky part of using NotifierProvider that I couldn't find elsewhere.

I made a notifier provider method updateMyClass(...) that I used in a button onPressed callback like this:

ref.read(myClassNotifierProvider.notifier).updateMyClass(...);

But the other Widget wasn't refreshing. It was trying to use another notifier provider method I made called getMyClass() to get the value and update when the value changed:

ref.read(myClassNotifierProvider.notifier).getMyClass();

No dice. Why? Should that ref.read instead be ref.watch? Nope, makes no difference.

Solution

The issue is solved by the following bit of code called somewhere in the Widget's build method (probably at the beginning):

ref.watch(myClassNotifierProvider);

See it? That lack of the .notifier call? That is what Riverpod uses to know it should keep an eye on this Widget and refresh if needed when the value changes. Of course you also need to set the state in the updateMyClass(...) method to trigger the update too (code below).

I was assuming that calling the ref.watch with the .notifier call was the same as calling it without the .notifier call. Not sure why it works this way, but it does kind of make sense; watching the .notifier is maybe only looking at the basic class behind the values. Probably wouldn't want to refresh the Widget for every single method call. I guess the non-notifier ref.watch can actually see the values, and tell the Widget to rebuild since the value changed.

Code

import 'package:riverpod_annotation/riverpod_annotation.dart';

// Don't forget this part :)
part 'my_class_provider.g.dart';

class MyClass {
  const MyClass(this.valueToUpdate);

  final int valueToUpdate;
}

@riverpod
class MyClassNotifier extends _$MyClassNotifier {
  @override
  MyClass build() => const MyClass(42);

  void updateMyClass(int newValue) => state = MyClass(newValue);

  MyClass getMyClass() => state;
}

Upvotes: 0

Charles
Charles

Reputation: 1241

Providers annotated with @riverpod will be disposed automatically when it is not being watched or listened. It seems like your counterProvider is getting disposed when switching to another tab.

You can add a print statement to verify that.

@override
int build() {
  ref.onDispose(() {
    print('disposed');
  });
  return 2;
}

Upvotes: 2

Related Questions