AsyncValue: loading, error, data
AsyncValue<T> unifies the three states of async work in one type: AsyncLoading, AsyncError, AsyncData. In practice that removes the need to maintain isLoading, error and data fields by hand and to handle their impossible combinations (loading and error at once).
In Riverpod 3, AsyncValue became sealed. That works together with Dart 3 pattern matching: a switch is checked for exhaustiveness by the compiler. Also, valueOrNull was renamed to value.
// 1) Pattern matching (rəsmi sənəddəki üslub):
final activity = ref.watch(activityProvider);
return switch (activity) {
AsyncValue(:final value?) => Text(value.title),
AsyncValue(:final error?) => Text('Xəta: $error'),
_ => const CircularProgressIndicator(),
};
// 2) when — daha tanış forma:
return activity.when(
data: (value) => Text(value.title),
error: (error, stackTrace) => Text('Xəta: $error'),
loading: () => const CircularProgressIndicator(),
);
// 3) Pull-to-refresh: refresh(provider.future) indikator üçün Future qaytarır.
RefreshIndicator(
onRefresh: () => ref.refresh(activityProvider.future),
child: ListView(children: [/* ... */]),
);The pattern-matching style from the official docs, and the `when` alternative.
| State | What to show | Typical mistake |
|---|---|---|
| First load (`AsyncLoading`, no value) | A skeleton or a spinner | Showing an empty screen |
| Refresh (a value exists, `isLoading` is true) | The old content plus a small indicator | Showing a spinner and losing the content — the screen "jumps" |
| Error with no value | An error message plus a retry button | Leaving an endless spinner |
| Error with a previous value | The old content plus a warning banner or snackbar | Replacing the whole screen with an error |
Automatic retry in Riverpod 3. Per the docs, when a provider throws during computation it is retried automatically — up to 10 times, with an exponential backoff from 200 ms to 6.4 seconds. However, Error instances (unrecoverable bugs) and ProviderException are not retried. The behaviour is configured with the retry parameter on a provider, on ProviderScope or on ProviderContainer; retry: (retryCount, error) => null disables it entirely.
Three details worth knowing for interviews when working with async state:
- `AsyncValue.guard` — instead of writing
try/catch, it converts a thrown error intoAsyncError:state = await AsyncValue.guard(() => repo.save(todo)); - `provider.future` — gives you an async provider's result as a
Future; used asawait container.read(provider.future)in tests and withRefreshIndicatorin the UI. - Optimistic updates — write the change into state immediately, then send it to the server, rolling back on failure. The fastest-feeling approach for users, but the rollback logic must be written.
📚 Sources and documentation
- Pull-to-refresh and AsyncValueofficialriverpod.dev
The switch pattern-matching and ref.refresh(provider.future) example.
- Automatic retryofficialriverpod.dev
The 10 attempts, the 200 ms → 6.4 s backoff and the retry parameter are documented here.
- What's new in Riverpod 3officialriverpod.dev
AsyncValue becoming sealed and valueOrNull being renamed to value.
- Cancelling requestsofficialriverpod.dev