when to use

methodusageexample
ref.watch()For UIRebuilds widget when value changes
ref.read()For callbacke.g. button onPressed
ref.listen()Side effectse.g. snackbar navigation

Building a widget based on an async value

    final dareDataAsync = ref.watch(dareDataProvider(widget.docId));

    return switch (dareDataAsync) {
      AsyncLoading() => buildScreenLoading(),
      AsyncError() => buildScreenError(
        message: UserMessage.darePaymentDetailLoadError,
        onRetry: () => setState(() {
          _dareData = null;
        }),
      ),
      AsyncData() => _buildDetail(user),
    };

Watching

    final hasMediaAsync = ref.watch(userHasMediaProvider(uid));
    final hasMedia = hasMediaAsync.value ?? false;

setting globals


    final friendUser = ref.watch(userByIdProvider(uidToFetch));
    friendUser.whenData((user) {
      _friendUser = user;
    });

Helpers

@riverpod
Future<UserModel> userById(Ref ref, String uid) async {
  // 1. Listen to the cache safely
  final userServiceState = ref.watch(userServiceProvider);
  final userInCache = userServiceState.users[uid];

  if (userInCache != null) {
    return userInCache;
  }

  // 2. If missing, fetch from the network without updating the master map inside this frame loop.
  // Instead of letting the Service change its master map instantly, 
  // we fetch the network data cleanly in isolation first.
  final fetchedUser = await AdapterRegistry.user.get(uid);

  // 3. Defer the state cache update to the next microtask frame.
  // This satisfies Riverpod's lifecycle rules completely.
  Future.microtask(() {
    if (ref.mounted) {
      ref.read(userServiceProvider.notifier).addUserToCache(fetchedUser);
    }
  });

  return fetchedUser;
}

Watching the provider

ref.watch(networkServiceProvider).isOnline
  • This watches the NetworkServiceState object (the value returned by the build method).
  • How it works: Every single time the network state updates (e.g., swapping from online to offline), a brand new NetworkServiceState object is emitted.
  • The result: Riverpod detects this new state object, extracts the .isOnline boolean, and triggers a rebuild of your convenience provider (and any UI components listening to it).
  • When to use it: Use this when you want your convenience provider to actively react and update whenever the network connection changes.

Watcching the notifier

ref.watch(networkServiceProvider.notifier).isOnline
  • This watches the NetworkService controller class instance itself.
  • How it works: In Riverpod, the notifier class instance (NetworkService) is instantiated exactly once when the provider is first initialized and is marked as persistent.
    • The controller instance itself never changes, even when its internal state updates.
  • The result: Because the controller instance never changes, ref.watch(…notifier) reads the value once and then stops listening for changes.
    • Your convenience provider will completely miss subsequent network changes and will get stuck on whatever the initial boot value was.
  • When to use it: You should almost never use ref.watch(…notifier) to read a value.
    • The .notifier syntax should strictly be used with ref.read when you want to call a method
ref.read(friendServiceProvider.notifier).fetchMyFriends()

Summary Checklist

Code SyntaxDoes it automatically update on network changes?Intended Use Case
ref.watch(networkServiceProvider).isOnlineYesListening to reactive state in UI or other providers.
ref.watch(networkServiceProvider.notifier).isOnlineNo (Stuck on initial value)Anti-pattern. Avoid using .notifier to grab state fields.

Corrected Convenience Provider Code

To ensure your convenience providers are fully reactive and update seamlessly, update them to watch the state directly:

/// Quick access to online status (Fully Reactive)
@riverpod
bool isOnline(Ref ref) {
  // Watches the state object, so this triggers whenever network status changes
  return ref.watch(networkServiceProvider).networkStatus == NetworkStatus.online;
}

Testing

test("Counter increments", () {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  expect(container.read(counterProvider), 0);
  container.read(counterProvider.notifier).state++;
  expect(container.read(counterProvider), 1);
});