skip to content

In Riverpod 3, how does AsyncValue.requireValue let a provider combine two async providers without await, and when is it dangerous inside a widget?

level: seniorimportance: should knowfreq 28%

answer

  1. value, or throw
  2. loading throws AsyncValueIsLoadingException
  3. silenced inside async providers
  4. same-frame recomputation
  5. widgets need loading handled above

basics

~20 s

requireValue returns the data if any exists, rethrows the error wrapped in ProviderException, or throws AsyncValueIsLoadingException while loading. Inside a FutureProvider or AsyncNotifier that loading exception is silenced, so ref.watch(a).requireValue combines providers synchronously; in a widget it crashes the build.

solid answer

~40 s

`requireValue` returns `value` whenever `hasValue` is true - including during a refresh or after an error that kept old data. With no data it throws: a `ProviderException` wrapping the error, or an `AsyncValueIsLoadingException` while loading. Riverpod catches the loading exception when it is thrown during an async provider's creation and simply keeps that provider loading. Combined with `ref.watch`, a non-`async` `FutureProvider` can write `ref.watch(newsFeedProvider).requireValue` and `ref.watch(mutedSourcesProvider).requireValue`, and it recomputes in the same frame either input changes, with no `await ref.watch(p.future)` gap. The source notes this is valid only in providers that create a `FutureOr` - `FutureProvider` or `AsyncNotifierProvider`. In a widget, nothing silences it: a `requireValue` during loading or error throws out of `build`, so use it only below a parent that already rendered loading and error.

code

dart · 24 lines
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final filteredFeedProvider = FutureProvider<List<Article>>((ref) {
  final articles = ref.watch(newsFeedProvider).requireValue;
  final muted = ref.watch(mutedSourcesProvider).requireValue;
  return [
    for (final article in articles)
      if (!muted.contains(article.source)) article,
  ];
});

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return switch (ref.watch(filteredFeedProvider)) {
      AsyncData(:final value) => ArticleList(articles: value),
      AsyncError(:final error) => FeedError(message: '$error'),
      AsyncLoading() => const Center(child: CircularProgressIndicator()),
    };
  }
}

go deeper

for a junior

Remember that requireValue gives the data or throws, and should not replace a proper switch over loading and error in screens.

for a middle

Explain the three outcomes of requireValue, including returning old data during refresh, and the silenced loading exception in async providers.

for a senior

Use requireValue chaining to remove await-induced flicker between derived providers, and enforce that widgets only use it below a loading gate.

for a principal

Decide where the app's loading gates sit so most widgets can assume data, keeping async handling in a few well-tested places.

## What requireValue does `requireValue` is the 'I am sure there is data' accessor on `AsyncValue`. Its logic, from the Riverpod 3.4 source: 1. If `hasValue` is true, return `value`. This includes states that also carry loading or an error, such as a refreshing feed or an error after data. 2. Else, if `hasError`, throw a `ProviderException` wrapping the error and stack trace. 3. Else - loading with no data - throw an **`AsyncValueIsLoadingException`**. | State | `value` | `requireValue` | |---|---|---| | `AsyncData` | data | data | | refreshing, old data present | old data | old data | | `AsyncError`, old data present | old data | old data | | `AsyncError`, no data | `null` | throws `ProviderException` | | first `AsyncLoading` | `null` | throws `AsyncValueIsLoadingException` | ## Synchronous chaining inside providers The interesting use is in providers. A filtered news feed needs the headlines and the user's muted sources, both async: ```dart final filteredFeedProvider = FutureProvider<List<Article>>((ref) { final articles = ref.watch(newsFeedProvider).requireValue; final muted = ref.watch(mutedSourcesProvider).requireValue; return [ for (final article in articles) if (!muted.contains(article.source)) article, ]; }); ``` Note the body is **not** `async`. What happens: - While either input is still loading, `requireValue` throws `AsyncValueIsLoadingException`. Riverpod recognises it during an async provider's creation and keeps `filteredFeedProvider` in loading instead of reporting an error; its `.future` stays pending. - Because both reads are `ref.watch`, the provider rebuilds when an input emits. - When both have data, the body returns synchronously, so the filtered list updates in the **same frame** as the upstream change. - If an input fails with no data, the thrown `ProviderException` puts this provider into error. ## Compared with awaiting futures ```dart final filteredFeedProvider = FutureProvider<List<Article>>((ref) async { final articles = await ref.watch(newsFeedProvider.future); final muted = await ref.watch(mutedSourcesProvider.future); return articles.where((a) => !muted.contains(a.source)).toList(); }); ``` This is also correct, but every rebuild passes through an `await`, so the combined provider goes through a loading state even when both inputs already have data. The source documents the `requireValue` form as the way to get the same-frame update. The constraint: it is valid only in providers that create a `FutureOr`, such as `FutureProvider` or `AsyncNotifierProvider.build`; a plain `Provider` would surface the exception as an error. ## Why it is dangerous in widgets Nothing catches `AsyncValueIsLoadingException` in a widget's `build`. `ref.watch(newsFeedProvider).requireValue` in a screen that can be built while the feed is loading throws, and Flutter shows its error widget. Safe uses: - A child widget built only inside the `AsyncData` branch of a parent's switch. - An app that preloads a provider at startup and shows the main UI only once it has data. Anywhere else, switch over the `AsyncValue` or use `value` with a null check. ## Review checklist - `requireValue` in a provider: is the provider an async one? - `requireValue` in a widget: is loading and error handled above it? - An `await ref.watch(p.future)` chain that causes flicker: consider the `requireValue` form. ## A note on errors Because `requireValue` checks `hasValue` before `hasError`, a provider built this way keeps producing a result from stale inputs after one of them fails with previous data. That is usually desirable for a feed, but for data that must be current - a balance, a permission - check `hasError` explicitly before trusting the value.

  • Why would requireValue inside a plain Provider not work the same way?
    The silencing of `AsyncValueIsLoadingException` happens where Riverpod turns an async provider's creation into an `AsyncValue`. A plain `Provider` has no loading state to fall back to, so the exception becomes the provider's error. The source limits the pattern to providers that create a `FutureOr`, such as `FutureProvider` and `AsyncNotifierProvider`.
  • What does requireValue return while a news feed is refreshing after invalidate?
    The old articles. During a refresh the state still has data, and `requireValue` returns `value` whenever `hasValue` is true, whatever loading or error flags are set alongside it. It throws only when there is no data at all.

saying these in an interview costs you the question

  • requireValue waits for the future to complete before returning
  • requireValue throws whenever isLoading is true, even with old data
  • Using requireValue in any widget build is safe because Riverpod catches it
  • requireValue returns null during loading
  • await ref.watch(p.future) and requireValue have identical timing