In Riverpod 3, how does AsyncValue.requireValue let a provider combine two async providers without await, and when is it dangerous inside a widget?
answer
- value, or throw
- loading throws AsyncValueIsLoadingException
- silenced inside async providers
- same-frame recomputation
- widgets need loading handled above
basics
~20 srequireValue 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 linesimport '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
Remember that requireValue gives the data or throws, and should not replace a proper switch over loading and error in screens.
Explain the three outcomes of requireValue, including returning old data during refresh, and the silenced loading exception in async providers.
Use requireValue chaining to remove await-induced flicker between derived providers, and enforce that widgets only use it below a loading gate.
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