Riverpod 3.0: The Complete Guide for Flutter Developers
Published on March 05, 2026
Riverpod 3.0: The Complete Guide for Flutter Developers
Flutter developers often struggle with state management — keeping their apps organized, testable, and maintainable. Riverpod 3.0 is a modern solution that improves on Provider with better safety, flexibility, and scalability.
In this article, we’ll explore Riverpod 3.0, its key features, and practical examples for your Flutter projects.
What is Riverpod?
Riverpod is a reactive dependency injection and state management library. Unlike the older Provider package:
- It’s compile-time safe
- Supports global and local providers
- Works seamlessly with Flutter and non-Flutter code
- Simplifies testing and mocking
Riverpod 3.0 brings even more improvements, like better syntax, improved code completion, and robust state handling.
Why use Riverpod 3.0?
- Safe state management: Compile-time safety prevents common bugs.
- Scalable architecture: Works well for large apps.
- Testable code: Easily mock providers for testing.
- Async-friendly: Supports FutureProvider and StreamProvider natively.
Getting Started
Add the dependency in pubspec.yaml:
dependencies:
flutter_riverpod: ^3.2.1Wrap your app with ProviderScope:
void main() {
runApp(
ProviderScope(child: MyApp()),
);
}
ProviderScopeis required to store the state of all providers.
Core Providers in Riverpod 3.0
1. Provider
For read-only values:
final greetingProvider = Provider<String>((ref) {
return 'Hello, Riverpod 3!';
});
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}2. StateProvider
For simple mutable state:
final counterProvider = StateProvider<int>((ref) => 0);
ref.watch(counterProvider); // reactive
ref.read(counterProvider.notifier).state++; // update3. StateNotifierProvider
For complex state management:
class Counter extends StateNotifier<int> {
Counter(): super(0);
void increment() => state++;
}
final counterNotifierProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
Use it in a widget:
final count = ref.watch(counterNotifierProvider);
ref.read(counterNotifierProvider.notifier).increment();4. FutureProvider
For async data (API calls, DB fetch):
final userProvider = FutureProvider<String>((ref) async {
await Future.delayed(Duration(seconds: 2));
return 'John Doe';
});
ref.watch(userProvider).when(
data: (user) => Text('User: $user'),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text('Error: $err'),
);final numberStreamProvider = StreamProvider<int>((ref) async* {
for (int i = 0; i < 10; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
});Advanced Features
- Provider overrides → Mock API in tests
- Scoped providers → Only available in a part of the widget tree
- Theme management → Easily switch app themes with StateProvider
- Persistent state → Combine with Hive, SharedPreferences
Best Practices
- Keep logic separate from UI
- Use StateNotifierProvider for anything more than a simple counter
- Always prefer ref.watch for reactive updates and ref.read for one-time reads
- Use ProviderScope overrides in tests