Riverpod 3.0 vs BLoC 9: Which State Management to Choose in 2026
The choice of state management in Flutter is an architectural decision that it impacts the entire code base. In 2026, the panorama has become clearer: Riverpod 3.0 e BLoC 9 are the two solutions production-ready that dominate the market, with features, trade-offs and profoundly different use cases.
Provider, the previous solution by Remi Rousselet (the same author of Riverpod), and officially in a state of maintenance. GetX lost significant share for testability problems and excessive coupling. 72% of Flutter projects enterprise launches in 2025-2026 use Riverpod or BLoC — and figure out which one to choose for your specific context it can make the difference between an architecture that scales and one that becomes an obstacle.
What You Will Learn
- The key architectural differences between Riverpod 3.0 and BLoC 9
- Like Riverpod it uses compile-time safety to prevent runtime errors
- Why BLoC is the standard in regulated industries (fintech, healthcare)
- Boilerplate comparison: how much code is needed for the same feature
- Migration strategies from Provider to Riverpod
- Decision matrix: how to choose based on the project context
Riverpod 3.0: The Mental Model
Riverpod is based on one central concept: i provider they are objects
immutable globals that expose reactive state. Unlike Provider (the package),
Riverpod does not use InheritedWidget: the providers are globally defined and accessible
from anywhere in the app via a Ref.
The main innovation of Riverpod 3.0 is the compile-time safety complete via code generation: All providers are automatically generated with types fixed, eliminating an entire category of runtime errors that plagued the versions precedents.
// Riverpod 3.0 con code generation (riverpod_generator)
// file: user_repository.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user_repository.g.dart';
// Provider semplice: espone un repository
@riverpod
UserRepository userRepository(Ref ref) {
return UserRepository(
dio: ref.watch(dioProvider),
);
}
// AsyncNotifier: gestisce stato asincrono
@riverpod
class UserList extends _$UserList {
@override
Future<List<User>> build() async {
// Carica la lista utenti al primo accesso
return ref.read(userRepositoryProvider).fetchUsers();
}
Future<void> addUser(User user) async {
// Ottimistic update
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await ref.read(userRepositoryProvider).createUser(user);
return ref.read(userRepositoryProvider).fetchUsers();
});
}
}
// Nel widget: ConsumerWidget
class UserListWidget extends ConsumerWidget {
const UserListWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(userListProvider);
return usersAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Errore: $error'),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) => UserTile(user: users[index]),
),
);
}
}
The Advantages of Riverpod 3.0
- Compile-time safety: the generated providers have verified types at compile time. Unable to access a non-existing provider.
- Minimum boilerplate: with code generation, a complete feature requires 30-40% less code than BLoC.
- Granular reactivity: each widget only subscribes to providers that it uses, minimizing unnecessary rebuilds.
- Provider override for testing: in tests and trivial to replace any provider with a mock, without external dependency injection frameworks.
- Automatic deletion: providers are arranged automatically when there are no more listeners, preventing memory leaks.
BLoC 9: The Mental Model
BLoC (Business Logic Component) implements a stream-based architecture: widgets they send Event to the BLoC, the BLoC processes events and outputs Stay updated, and the widgets rebuild in response to the new states. The flow is always one-way: Event -> BLoC -> State -> UI.
BLoC 9 introduces native support for sealed classes in Dart 3 per events and states, eliminating runtime casts and making pattern matching exhaustive.
// BLoC 9 con sealed classes (Dart 3)
// file: auth_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
// Events: sealed class per type-safety
sealed class AuthEvent {}
final class LoginRequested extends AuthEvent {
const LoginRequested({required this.email, required this.password});
final String email;
final String password;
}
final class LogoutRequested extends AuthEvent {}
// States: sealed class per exhaustive pattern matching
sealed class AuthState {}
final class AuthInitial extends AuthState {}
final class AuthLoading extends AuthState {}
final class AuthAuthenticated extends AuthState {
const AuthAuthenticated({required this.user});
final User user;
}
final class AuthError extends AuthState {
const AuthError({required this.message});
final String message;
}
// BLoC
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc({required AuthRepository authRepository})
: _authRepository = authRepository,
super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
on<LogoutRequested>(_onLogoutRequested);
}
final AuthRepository _authRepository;
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
final user = await _authRepository.login(
email: event.email,
password: event.password,
);
emit(AuthAuthenticated(user: user));
} catch (error) {
emit(AuthError(message: error.toString()));
}
}
Future<void> _onLogoutRequested(
LogoutRequested event,
Emitter<AuthState> emit,
) async {
await _authRepository.logout();
emit(AuthInitial());
}
}
// Nel widget: BlocBuilder
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
return switch (state) {
AuthInitial() => LoginForm(),
AuthLoading() => const CircularProgressIndicator(),
AuthAuthenticated(:final user) => HomePage(user: user),
AuthError(:final message) => ErrorWidget(message: message),
};
},
);
}
}
The Advantages of BLoC 9
- Native audit trail: every event and every state transition is traceable. In financial and healthcare systems, this traceability is often a regulatory requirement.
- Total separation of concerns: the UI does not know how they are processed the events. The BLoC does not know how the status is displayed. Excellent tested.
- Predictability: one-way flow makes the behavior of the app completely deterministic.
- Mature ecosystem: flutter_bloc, hydrated_bloc, replay_bloc, bloc_concurrency — packages tested in production for years.
- Team scalability: The rigid structure of BLoC is easier to follow large teams where not all developers have the same level.
Boilerplate Comparison: Same Feature in Riverpod and BLoC
| I wait | Riverpod 3.0 | BLoC 9 |
|---|---|---|
| File for simple feature | 1-2 rows | 3-4 files (event, state, bloc, widget) |
| Lines of code for CRUD feature | ~80 lines | ~140 lines |
| Initial setup | ProviderScope in main() | BlocProvider in the widget tree |
| Testing | ProviderContainer + override | BlocTest + mock repository |
| Async handling | Native AsyncValue | Explicit states (Loading, Success, Error) |
| Code generation | Required (riverpod_annotation) | Optional |
Decision Matrix: How to Choose
Use this decision matrix to choose the right solution for your context:
Choose Riverpod 3.0 if:
- The team is small (1-5 developers) and you want maximum productivity
- The app has many complex asynchronous states (API calls, streams)
- You want minimal boilerplate without sacrificing type safety
- The project is new and you can adopt code generation from the beginning
- You already have experience with Provider and want a natural upgrade
Choose BLoC 9 if:
- You work in a regulated industry (fintech, healthcare, insurance)
- The team is large (6+ developers) with heterogeneous levels of experience
- You have audit trail and event traceability requirements
- Prefer a rigid structure that enforces best practices
- The app already exists with BLoC and refactoring is not justified
Migration from Provider to Riverpod
If you have an existing app with Provider, migration to Riverpod can happen
incrementally. The package riverpod_lint includes a series
of automatic migration codemods:
# Aggiungi le dipendenze
flutter pub add riverpod riverpod_annotation
flutter pub add --dev riverpod_generator build_runner riverpod_lint
# Esegui il migration codemod
dart run riverpod_cli migrate
# Prima: Provider (vecchio)
# final userProvider = Provider<UserRepository>((ref) {
# return UserRepository();
# });
# Dopo: Riverpod 3.0 (nuovo) - generato da riverpod_generator
# @riverpod
# UserRepository userRepository(Ref ref) {
# return UserRepository();
# }
Next Steps
Now that you have the complete comparison picture, the next article in the series explores Riverpod in a practical way: how to structure a complete feature with AsyncNotifier, how to use code generation to eliminate the boilerplate, and how to write unit and widget tests with the ProviderContainer and overrides.
Conclusions
There is no universal answer between Riverpod and BLoC — there is a right answer for your specific context. Riverpod excels in productivity, ergonomics and asynchronous state management. BLoC excels at audit trails, team scalability and regulatory compliance.
Whatever the choice, both solutions produce testable, maintainable apps and scalable if used correctly. The determining factor in practice is not so much the library chosen as well as the discipline with which the principles are applied architectural features that that library promotes.







