In Riverpod 3, a product-detail FutureProvider keeps retrying while the device is offline; what is the default retry policy, and how do you customise or disable it?
answer
- new default in 3.0
- 200 ms doubling to 6.4 s
- at most 10 retries
- Error and ProviderException skipped
- return null to stop
basics
~20 sRiverpod 3 retries a provider that throws while building: up to 10 times, starting at 200 ms and doubling to a 6.4 s cap, skipping Error subclasses and ProviderException. Pass retry: to the provider or to ProviderScope; returning null stops retrying.
solid answer
~40 sSince 3.0, a provider whose build throws is retried automatically. The default, `ProviderContainer.defaultRetry`, allows 10 retries with exponential backoff from 200 ms, doubling up to 6.4 s, and skips two kinds of failure: `Error` subclasses such as `TypeError`, which are bugs, and `ProviderException`, which means the provider only rethrew a failing dependency - that dependency retries instead. You customise it with a function `Duration? Function(int retryCount, Object error)`: return a delay to retry, `null` to stop. Pass it as `retry:` on a provider's constructor, or on `ProviderScope`/`ProviderContainer` for everything; a provider's own function wins. While a retry is pending the `AsyncValue` carries the error with `retrying` true, and `await ref.watch(p.future)` keeps waiting until success or the retries run out. Disposal cancels a pending retry.
code
dart · 23 linesimport 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Duration? productRetry(int retryCount, Object error) {
if (error is ProductNotFound) return null;
if (retryCount >= 5) return null;
return Duration(seconds: 1 << retryCount);
}
final productDetailProvider = FutureProvider.autoDispose
.family<Product, String>(retry: productRetry, (ref, productId) async {
final api = ref.watch(catalogApiProvider);
return api.fetchProduct(productId);
});
void main() {
runApp(
ProviderScope(
retry: (retryCount, error) => retryCount >= 3 ? null : const Duration(seconds: 2),
child: const CatalogApp(),
),
);
}go deeper
Know that Riverpod 3 retries failing providers automatically and that returning null from a retry function turns it off.
State the default backoff and limit, the exclusions for Error and ProviderException, and where retry is configured.
Design retry policies that stop on permanent domain errors, bound offline request storms across families, and explain retry's interaction with .future and disposal.
Decide an app-wide retry policy with backend and product input, balancing perceived reliability against server load and error visibility.
## What changed in Riverpod 3 In Riverpod 2, a provider that threw simply held the error until something invalidated it. Riverpod 3 introduced **automatic retry**: when a provider fails while computing its value - a synchronous throw or a failed `Future` - Riverpod schedules another attempt with backoff. For a product-detail screen on a flaky connection, that often means the user sees the product appear without touching a retry button. ## The default policy The default lives in `ProviderContainer.defaultRetry`. Reading its source gives the exact rules: | Rule | Value | |---|---| | Maximum retries | 10 | | First delay | 200 ms | | Growth | doubles each retry | | Delay cap | 6.4 s | | `Error` subclasses (`TypeError`, `StateError`, ...) | not retried | | `ProviderException` | not retried | The delays run 200 ms, 400, 800, 1600, 3200, then 6.4 s for the remaining attempts. The two exclusions are deliberate. An `Error` indicates a programming bug; retrying it would only fill the logs. A `ProviderException` means this provider did not fail itself - it rethrew the failure of a provider it depends on - so retrying it cannot help; the failing dependency is the one that retries. Note that the 3.0 'what's new' page describes retry as continuing until success; the source and the retry concept page cap it at 10. ## Customising A retry function has the shape `Duration? Function(int retryCount, Object error)`. `retryCount` starts at 0. Returning a `Duration` schedules the next attempt after that delay; returning `null` stops and exposes the error. ```dart Duration? productRetry(int retryCount, Object error) { if (error is ProductNotFound) return null; if (retryCount >= 5) return null; return Duration(seconds: 1 << retryCount); } ``` Where to set it: 1. **Per provider**: the `retry:` constructor parameter, for example `FutureProvider.autoDispose.family<Product, String>(retry: productRetry, (ref, id) async {...})`. 2. **Globally**: `ProviderScope(retry: ..., child: ...)` or `ProviderContainer(retry: ...)`. A child container inherits its parent's function. 3. **Precedence**: the provider's own function, then the container's, then the default. To **disable** retry, return `null` unconditionally: `retry: (retryCount, error) => null`. ## Interactions worth knowing - **State while waiting**: the provider exposes the error with `AsyncValue.retrying` set to true while another attempt is scheduled. How to render that is `AsyncValue` handling. - **`.future`**: `await ref.watch(productDetailProvider(id).future)` skips intermediate failures and completes on success, or with the error once retries are exhausted. - **Disposal cancels retries**: leaving an autoDispose product screen disposes the provider, which cancels the pending retry timer, so abandoned screens stop hammering the network. - **Dependency changes reset the count**: when a watched dependency changes, the retry counter starts again from 0. ## Production judgement - A 404 or a validation failure will never succeed; exclude such domain exceptions so the user sees the error at once. - Offline storms: across many family instances, the default can still add up to many requests; a global function can check connectivity or cap attempts lower. - Tests usually want failures to surface immediately; retry configuration for test containers belongs with the testing tools.
- Why does the default policy skip ProviderException?A `ProviderException` is thrown when a provider rethrows the failure of a provider it watches. The dependent did not fail on its own, so retrying it would repeat the same rethrow. The failing upstream provider retries instead, and when it succeeds the dependent rebuilds. A side effect: disabling retry on the upstream provider means its dependents also stop recovering.
- The user leaves the product screen while retries are pending; do they continue?No, provided the provider is autoDispose. Losing the last listener disposes it, and disposal cancels the pending retry timer. A provider without autoDispose keeps its state - and its retry schedule - because nothing disposes it.
saying these in an interview costs you the question
- Riverpod 3 retries failing providers forever until they succeed
- A TypeError in build is retried like a network failure
- Retry is configured only globally, never per provider
- Returning Duration.zero is how you disable retry
- Retries keep running after an autoDispose provider is disposed