Overview of Riverpod offline persistence
Storage interface to interact with the database and the AnyNotifier.persist function used within notifiers to opt-in to persistence.website·Indexed 35 days ago
https://riverpod.dev/A reactive caching and state management framework for Flutter. The documentation covers core concepts such as providers, families, scoping, and automatic disposal, as well as advanced features like mutations and offline persistence introduced in Riverpod 3.0. It includes migration guides from Provider, ChangeNotifier, and StateNotifier, as well as tutorials on code generation, hooks, and testing providers in isolation.
Storage interface to interact with the database and the AnyNotifier.persist function used within notifiers to opt-in to persistence.Code generation in Riverpod is an optional but recommended approach to defining providers. It replaces the manual instantiation of provider types (like FutureProvider or StateNotifierProvider) with annotations, allowing the generator to determine the most suitable provider type based on the return type of the function or class.
Key benefits include:
.family modifier, allowing named, optional, and default parameters.AsyncValue when a provider is marked as async.flutter_hooks package designed as an alternative to StatefulWidget to make local widget logic more reusable and composable. While Riverpod providers are used for "global" application state, hooks are used for local widget state, such as managing TextEditingController or AnimationController. They can also replace the "builder" pattern (e.g., FutureBuilder) to reduce nesting and improve readability. Hooks are particularly helpful for forms, animations, and reacting to user events.By default, Riverpod providers are automatically retried when an exception is thrown during computation. The default logic allows up to 10 retries with an exponential backoff ranging from 200ms to 6.4 seconds.
Crucially, the default logic will NOT retry:
Error objects: These are considered non-recoverable bugs.ProviderException objects: These indicate that the provider failed because a dependency it watches failed. In this case, only the root failing provider is retried.riverpod_lint package.Providers in Riverpod are essentially "memoized functions." They wrap a function and cache its result, allowing multiple widgets to access the same value without re-executing the logic (e.g., avoiding redundant network requests).
Key capabilities of providers include:
Ref.watch to combine caches and automatically invalidate them.FutureBuilder or StreamBuilder.Using ref in a widget that has been unmounted (e.g., after an await call) will throw the error: "Using 'ref' when a widget is about to or has been unmounted is unsafe." (or "Bad state: No ProviderScope found" in older versions).
To prevent this, check the mounted property of the BuildContext before accessing ref after any asynchronous operation.
ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // Safe to use ref here
}
)
ChangeNotifier to AsyncNotifier involves shifting from mutable state and manual listener notification to immutable state managed by Riverpod. This removes the need for manual loading/error flags (like isLoading or hasError) and tedious try/catch/finally blocks, as Riverpod handles asynchronous states via AsyncValue.Ref or Notifier after it has been disposed will throw an error. To prevent this, especially after asynchronous gaps (like await), use ref.mounted to verify if the provider is still active before proceeding.final provider = FutureProvider<int>((ref) async {
await Future.delayed(Duration(seconds: 1));
// Abort the provider if it has been disposed during the await.
if (!ref.mounted) throw MyException();
return 42;
});Provider<Item>) leads to unreliable behavior because the InheritedWidget API only retrieves the closest ancestor. Riverpod eliminates this limitation, allowing developers to split logic into multiple providers of the same type without conflict.final itemsProvider = Provider.autoDispose(
(ref) => <Item>[], // ...
);
final evenItemsProvider = Provider.autoDispose((ref) {
final items = ref.watch(itemsProvider);
return [...items.whereIndexed((index, element) => index.isEven)];
});Providers are the central feature of Riverpod and act as a description of state. To create a provider, declare a final top-level (global) variable. Providers are immutable and testable.
Key components of a provider declaration:
Provider (for synchronous values), FutureProvider (for Future values), and StreamProvider (for Stream values).autoDispose clears the cache when the provider is no longer used, and family allows passing arguments to the provider.Unlike package:provider, Riverpod allows multiple providers of the same return type.
final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((ref) => 'England');