Flutter State Management 2026: Riverpod vs Bloc vs Provider
Every Flutter developer eventually faces the question: Which state management should I use? After years in production with all three solutions, here's my honest assessment.
TL;DR
My recommendation for 2026: Bloc. The strict architecture pays off in the long run. But keep reading, it depends on your use case.
The Contenders at a Glance
Current Versions (February 2026)
- Riverpod
3.2.1(7.1k GitHub Stars) - Bloc
9.1.1(12.4k GitHub Stars) - Provider
6.1.5(Part of Flutter Favorites)
All three are production-ready and actively maintained.
Provider: The Classic
Provider is the official entry point for state management in Flutter. It's a wrapper around InheritedWidget that makes dependency injection simple.
Code Example: Counter with Provider
// 1. Define state
class CounterNotifier extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 2. Register provider
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterNotifier(),
child: const MyApp(),
),
);
}
// 3. Consume state
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterNotifier>();
return Text('Count: ${counter.count}');
}
}
Provider Pros
- Officially recommended by Flutter
- Easy to learn
- Minimal boilerplate
- Perfect Flutter DevTools integration
Provider Cons
- Depends on widget tree (
ProviderNotFoundExceptionanyone?) - Difficult with complex provider dependencies
- No compile-time safety
Bloc: The Enterprise Standard
Bloc (Business Logic Component) follows the event-state pattern and is known for strict architecture and testability.
Code Example: Counter with Bloc
// 1. Define events
sealed class CounterEvent {}
class IncrementPressed extends CounterEvent {}
// 2. Implement bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementPressed>((event, emit) {
emit(state + 1);
});
}
}
// 3. Connect UI
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(IncrementPressed()),
child: const Text('Increment'),
),
],
);
},
);
}
}
Bloc Pros
- Strict separation of UI and business logic
- Excellent testability via
bloc_test - Predictable state through event-sourcing
- Large community (12.4k GitHub Stars)
- Dedicated linting with
bloc_lint
Bloc Cons
- Lots of boilerplate (events, states, bloc classes)
- Steep learning curve for beginners
- Can be overkill for simple features
- Cubit as alternative is simpler but inconsistent
Riverpod: The Modern Approach
Riverpod (an anagram of "Provider") was developed by the same author to overcome Provider's limitations. With version 3.0 (September 2025), it's become a reactive caching and data-binding framework.
Code Example: Counter with Riverpod
// 1. Define provider (global, outside widget tree)
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
// 2. Wrap app with ProviderScope
void main() {
runApp(
ProviderScope(
child: const MyApp(),
),
);
}
// 3. Consume state
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Text('Increment'),
),
],
);
}
}
Async Data Handling: Riverpod's Killer Feature
@riverpod
Future<User> user(Ref ref) async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
return User.fromJson(jsonDecode(response.body));
}
// In the UI: Loading, error, and data handled automatically
class UserPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return switch (userAsync) {
AsyncData(:final value) => Text('Hello, ${value.name}'),
AsyncError(:final error) => Text('Error: $error'),
_ => const CircularProgressIndicator(),
};
}
}
Riverpod Pros
- Compile-time safety: no runtime exceptions
- Widget-tree independent: providers are global
- Excellent async handling: loading/error states out of the box
- Code generation with
riverpod_generatorreduces boilerplate - Auto-dispose: automatic memory management
- Reactive caching: inspired by React Query
Riverpod Cons
- Learning curve for the new mental model
- More setup due to code generation
- Smaller community than Bloc (7.1k vs 12.4k stars)
- Breaking changes between major versions
When to Use Which Pattern?
Use Provider when:
- You're learning Flutter
- Your app is very simple
- You want minimal dependencies
- Your team already knows Provider
Use Bloc when:
- You're working in a large team
- Strict architecture matters
- You need event-sourcing
- Enterprise environment with strict code reviews
Use Riverpod when:
- You work heavily with async data (APIs, databases)
- You want to use modern Dart features
- You value compile-time safety
- You're starting a new project (2025/2026)
What Do Large Flutter Apps Use?
- BMW, eBay, Google Pay: Primarily Bloc
- Many indie apps and startups: Trending toward Riverpod
- Legacy projects: Often still Provider
Code With Andrea's Recommendation
Andrea Bizzotto, one of the most respected Flutter educators, recommends Riverpod for new projects. His Flutter Architecture with Riverpod is considered best practice. His guide "Flutter Riverpod 2.0: The Ultimate Guide" is required reading.
Migration Between Solutions
Provider → Riverpod
Relatively easy since the author is the same. Riverpod can coexist with Provider for gradual migration.
Bloc → Riverpod
More involved since it's a completely different paradigm. Better to start with new features rather than migrating everything at once.
Provider → Bloc
Also involved. Bloc requires rethinking in terms of events/states.
My Personal Recommendation
For new projects in 2026: Bloc.
Why? The strict separation of events and states forces you to write clean code. Sure, it's more boilerplate, but from my experience I can tell you: that structure saves your ass when the project grows. The testability is unmatched, and with bloc_test you write tests that actually mean something.
Riverpod? Absolutely valid, especially if you work heavily with async data. The AsyncValue pattern is elegant. But I miss the strict architecture. It's too easy to write spaghetti code.
Provider? Only for very simple apps or for learning. For production, I wouldn't start with it today. If you are just getting started, my Flutter learning guide will help.
Conclusion
There's no "wrong" state management, only the wrong one for your use case. All three solutions are production-ready and used by large apps. When your state gets more complex, take a look at state machines for Flutter UIs. How to set up modules, dependency injection and lint rules around it is in Flutter architecture that does not rot.
My rule of thumb:
- Learning project → Provider
- Any serious project → Bloc
- Async-heavy apps (lots of API calls) → Riverpod as alternative
What do you use? Let me know on Twitter @robocitrus. I'm curious about your experiences!
This article is regularly updated. Last update: February 2026.



