Core Concepts & Ref Lifecycle
🎯 Learning Objective
Master the Ref object hierarchy, automatic cache disposal (autoDispose), state preservation (keepAlive), manual state invalidation (ref.invalidate()), lifecycle hooks (ref.onDispose()), and protective unmount guards (ref.mounted).
📋 Prerequisites
- Completion of Phase 1: Getting Started with Riverpod 3.
- Understanding of
ConsumerWidgetandref.watch().
1. The Ref Hierarchy Explained
In Riverpod, Ref is the central hub. It allows widgets to interact with providers, and allows providers to interact with other providers.
ConsumerWidget.build(). Allows Flutter UI widgets to watch, read, or listen to providers.2. State Lifecycle: autoDispose vs keepAlive()
By default in Riverpod 3 (with code generation), providers automatically dispose of their state when they are no longer watched by any widget. This prevents memory leaks.
Inside a provider's build() method, calling ref.keepAlive() prevents auto-disposal, keeping state cached until explicit invalidation.
3. Manual State Invalidation & Refresh
When data becomes stale (e.g. after a pull-to-refresh action or database mutation), you can force a provider to re-execute its build() method.
ref.invalidate(provider) — Lazy Invalidation
WHAT: Marks the provider as stale immediately. Destruction and re-fetching occur lazily when the provider is next watched.
BEST FOR: Triggering background cache invalidations after network POST/PUT mutations.
ref.refresh(provider) — Eager Re-Fetch
WHAT: Invalidates the provider and immediately forces a re-read, returning the new value.
BEST FOR: Pull-to-refresh UI gestures where the user expects immediate loading spinners.
ref.invalidateSelf() — Self Invalidation
WHAT: Called inside a provider notifier to force itself to re-execute its own build() method.
BEST FOR: Internal error recovery and retry logic inside Notifiers.
4. Provider Lifecycle Hooks & ref.mounted Safety
Providers can register cleanup callbacks that run when the provider is disposed or cancelled:
final timerProvider = NotifierProvider<TimerNotifier, int>(TimerNotifier.new);
class TimerNotifier extends Notifier<int> {
@override
int build() {
final timer = Timer.periodic(const Duration(seconds: 1), (_) => state++);
// Register cleanup callback on disposal
ref.onDispose(() {
timer.cancel(); // Prevents timer memory leaks
});
return 0;
}
}
When a provider performs an asynchronous operation (e.g. network call), the provider might be disposed before the request completes. Modifying state on an unmounted notifier will throw a fatal error.
Always protect async assignments: if (!ref.mounted) return;
5. Real Production Examples
Example 1: Auto-Disposing Search Input
Demonstrates auto-disposal when closing a search screen:
final searchQueryProvider = NotifierProvider.autoDispose<SearchQueryNotifier, String>(
SearchQueryNotifier.new,
);
class SearchQueryNotifier extends AutoDisposeNotifier<String> {
@override
String build() => '';
void updateQuery(String query) => state = query;
}
Example 2: Debounced Live Search with Network Cancellation
Production debounced API search notifier that cancels pending network calls on disposal using ref.onDispose():
class LiveSearchNotifier extends AutoDisposeNotifier<AsyncValue<List<String>>> {
@override
AsyncValue<List<String>> build() {
return const AsyncValue.data([]);
}
Future<void> search(String query) async {
final client = http.Client();
ref.onDispose(() {
client.close(); // Cancel active HTTP request if user leaves screen
});
state = const AsyncValue.loading();
try {
final res = await client.get(Uri.parse('https://api.example.com/search?q=$query'));
if (!ref.mounted) return; // Guard unmounted state
state = AsyncValue.data(parseResults(res.body));
} catch (err, stack) {
if (!ref.mounted) return;
state = AsyncValue.error(err, stack);
}
}
}
6. Knowledge Check & Mini Challenge
🏋️ Mini Challenge: Timer Cleanup
Write a StopwatchNotifier using ref.onDispose() to ensure a periodic Timer is cancelled whenever the provider is disposed.
Phase 02 Summary
You have mastered the Ref hierarchy, automatic disposal vs keepAlive, manual invalidation with ref.invalidate(), and safety guards using ref.mounted and ref.onDispose().