Flutter Riverpod 3.0 Architecture Guide: Notifier, AsyncNotifier, and AutoDispose Patterns

Flutter Riverpod 3.0 Architecture Guide: Notifier, AsyncNotifier, and AutoDispose Patterns
Riverpod, Flutter’s leading state management solution, has arrived with a major version 3.0 architectural overhaul. Legacy patterns like StateNotifier, ChangeNotifier, and manually created providers are now completely superseded by class-based Notifier / AsyncNotifier and standardized code generation via riverpod_generator.
For context, the @riverpod code generation approach has enabled autoDispose by default (releasing memory when screen routes are unmounted) since Riverpod 2.0 already. What 3.0 actually changes is unifying the previously fragmented provider type interfaces — AutoDispose* / Family* and friends — into a single Provider/Notifier API, simplifying the surface considerably.
In this article, we cover the core breaking changes in Riverpod 3.0 and practical production patterns for using Notifier, AsyncNotifier, and family parameters.
1. Comparing Legacy Riverpod vs. Riverpod 3.0 Standards
| Feature | Legacy Riverpod (1.x / 2.x) | Riverpod 3.0 Standard |
|---|---|---|
| Type Interface | Split across Provider / AutoDisposeProvider / FamilyProvider, etc. |
Unified into Provider/Notifier (default autoDispose itself has been in place since 2.0 for @riverpod code generation) |
| State Controllers | StateNotifierProvider / FutureProvider |
Notifier / AsyncNotifier with @riverpod annotation |
| Family Arguments | Manual .family wrappers |
Native method parameters in generated build methods |
| Async State Handling | Manual AsyncValue.when() handling |
Enhanced AsyncValue helpers & automatic state guarding |
2. Practical Notifier and AsyncNotifier Implementation
1) Synchronous State: Notifier Implementation
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
// @riverpod annotation enables autoDispose by default
@riverpod
class CounterNotifier extends _$CounterNotifier {
@override
int build() {
// Initial state setup (Re-evaluated on screen remounts)
return 0;
}
void increment() {
state = state + 1;
}
void decrement() {
state = state - 1;
}
}
2) Asynchronous State: AsyncNotifier & Family Implementation
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../models/user.dart';
part 'user_provider.g.dart';
// Pass family parameters directly as arguments to the build method
@riverpod
class UserDetailNotifier extends _$UserDetailNotifier {
@override
Future<User> build(String userId) async {
// Async data fetching
return await ref.read(userRepositoryProvider).fetchUser(userId);
}
Future<void> updateUserName(String newName) async {
// Set loading state
state = const AsyncLoading();
// Guarded state update
state = await AsyncValue.guard(() async {
final updatedUser = await ref
.read(userRepositoryProvider)
.updateName(arg, newName); // 'arg' accesses the family parameter (userId)
return updatedUser;
});
}
}
3. Consuming AsyncValue & Handling AutoDispose in Widgets
In the UI layer, subscribe to state updates using ref.watch and branch rendering using AsyncValue.when.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class UserProfileScreen extends ConsumerWidget {
final String userId;
const UserProfileScreen({super.key, required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Subscribe to provider with family parameter
final userAsync = ref.watch(userDetailNotifierProvider(userId));
return Scaffold(
appBar: AppBar(title: const Text('User Profile')),
body: userAsync.when(
data: (user) => Column(
children: [
Text('Name: ${user.name}'),
ElevatedButton(
onPressed: () {
ref
.read(userDetailNotifierProvider(userId).notifier)
.updateUserName('New Name');
},
child: const Text('Update Name'),
),
],
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
),
);
}
}
4. Production AutoDispose Considerations
- Global Auth & Settings State: For application-wide singletons (e.g., authentication tokens or global theme preferences), explicitly set
@Riverpod(keepAlive: true). - Dynamic
ref.keepAlive()Links: To temporarily preserve state while an async transaction completes after screen unmount, callfinal link = ref.keepAlive();and release it later vialink.close(). - Disposal Lifecycle: AutoDispose doesn’t release memory the instant the last listener disappears — it waits one frame’s worth of grace period, and only disposes the state if it’s still unused after that frame. Plan caching strategies (e.g. for temporarily saved form input) accordingly.
Summary and Conclusion
Riverpod 3.0 delivers a modern state management paradigm with minimal boilerplate and default memory safety.
[!NOTE]
- Migrate away from legacy
StateNotifierto class-basedNotifierandAsyncNotifier.- Standardize on
riverpod_generatorto automatefamilyparameter handling and provider definitions.- Understand default
autoDisposerules and explicitly annotate global providers with@Riverpod(keepAlive: true)to eliminate leaks.
Adopting Riverpod 3.0 elevated type safety and maintainability across Flutter codebases.