Riverpod 3.0 vs BLoC 9: Quale State Management Scegliere nel 2026
La scelta dello state management in Flutter e una decisione architettuale che si ripercuote sull'intera base di codice. Nel 2026, il panorama si e chiarito: Riverpod 3.0 e BLoC 9 sono le due soluzioni production-ready che dominano il mercato, con caratteristiche, trade-off e casi d'uso profondamente diversi.
Provider, la soluzione precedente di Remi Rousselet (lo stesso autore di Riverpod), e ufficialmente in stato di manutenzione. GetX ha perso quote significative per problemi di testabilita e accoppiamento eccessivo. Il 72% dei progetti Flutter enterprise avviati nel 2025-2026 usa Riverpod o BLoC — e capire quale scegliere per il tuo contesto specifico puo fare la differenza tra un'architettura che scala e una che diventa un ostacolo.
Cosa Imparerai
- Le differenze architetturali fondamentali tra Riverpod 3.0 e BLoC 9
- Come Riverpod usa la compile-time safety per prevenire errori runtime
- Perche BLoC e lo standard nelle industry regolamentate (fintech, healthcare)
- Confronto boilerplate: quanto codice serve per la stessa feature
- Strategie di migrazione da Provider a Riverpod
- Decision matrix: come scegliere in base al contesto del progetto
Riverpod 3.0: Il Modello Mentale
Riverpod si basa su un concetto centrale: i provider sono oggetti
globali immutabili che espongono stato reattivo. A differenza di Provider (il pacchetto),
Riverpod non usa InheritedWidget: i provider sono definiti a livello globale e accessibili
da qualsiasi punto dell'app tramite una Ref.
La novita principale di Riverpod 3.0 e la compile-time safety completa tramite code generation: tutti i provider vengono generati automaticamente con tipi corretti, eliminando una intera categoria di errori runtime che affliggevano le versioni precedenti.
// 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]),
),
);
}
}
I Vantaggi di Riverpod 3.0
- Compile-time safety: i provider generati hanno tipi verificati a compile-time. Impossibile accedere a un provider non esistente.
- Boilerplate minimo: con code generation, una feature completa richiede 30-40% meno codice rispetto a BLoC.
- Granular reactivity: ogni widget si iscrive solo ai provider che usa, minimizzando i rebuild inutili.
- Provider override per testing: nei test e triviale sostituire qualsiasi provider con un mock, senza dependency injection framework esterni.
- Cancellazione automatica: i provider vengono disposti automaticamente quando non ci sono piu listener, prevenendo memory leak.
BLoC 9: Il Modello Mentale
BLoC (Business Logic Component) implementa un'architettura basata su stream: i widget inviano Event al BLoC, il BLoC processa gli eventi e emette State aggiornati, e i widget ricostruiscono in risposta ai nuovi stati. Il flusso e sempre unidirezionale: Event -> BLoC -> State -> UI.
BLoC 9 introduce il supporto nativo a sealed classes in Dart 3 per events e states, eliminando i runtime cast e rendendo il pattern match esaustivo.
// 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),
};
},
);
}
}
I Vantaggi di BLoC 9
- Audit trail nativo: ogni event e ogni state transition e tracciabile. Nei sistemi finanziari e healthcare, questa traccabilita e spesso un requisito normativo.
- Separation of concerns totale: la UI non sa come vengono processati gli eventi. Il BLoC non sa come viene visualizzato lo stato. Testabilita eccellente.
- Predictability: il flusso unidirezionale rende il comportamento dell'app completamente deterministico.
- Ecosistema maturo: flutter_bloc, hydrated_bloc, replay_bloc, bloc_concurrency — pacchetti testati in produzione per anni.
- Team scalability: la struttura rigida di BLoC e piu facile da far seguire a team grandi dove non tutti i developer hanno lo stesso livello.
Confronto Boilerplate: La Stessa Feature in Riverpod e BLoC
| Aspetto | Riverpod 3.0 | BLoC 9 |
|---|---|---|
| File per feature semplice | 1-2 file | 3-4 file (event, state, bloc, widget) |
| Linee di codice per CRUD feature | ~80 righe | ~140 righe |
| Setup iniziale | ProviderScope nella main() | BlocProvider nel widget tree |
| Testing | ProviderContainer + override | BlocTest + mock repository |
| Async handling | AsyncValue nativo | Stati espliciti (Loading, Success, Error) |
| Code generation | Richiesto (riverpod_annotation) | Opzionale |
Decision Matrix: Come Scegliere
Usa questa decision matrix per scegliere la soluzione giusta per il tuo contesto:
Scegli Riverpod 3.0 se:
- Il team e piccolo (1-5 developer) e vuoi massima produttivita
- L'app ha molti stati asincroni complessi (API calls, stream)
- Vuoi il minimo boilerplate senza sacrificare la type safety
- Il progetto e nuovo e puoi adottare code generation sin dall'inizio
- Hai gia esperienza con Provider e vuoi un upgrade naturale
Scegli BLoC 9 se:
- Lavori in un'industry regolamentata (fintech, healthcare, insurance)
- Il team e grande (6+ developer) con livelli di esperienza eterogenei
- Hai requisiti di audit trail e traccabilita degli eventi
- Preferisci una struttura rigida che imponga le best practice
- L'app esiste gia con BLoC e il refactoring non e giustificato
Migrazione da Provider a Riverpod
Se hai un'app esistente con Provider, la migrazione a Riverpod puo avvenire
incrementalmente. Il pacchetto riverpod_lint include una serie
di migration codemods automatici:
# 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
Ora che hai il quadro comparativo completo, il prossimo articolo della serie approfondisce Riverpod in modo pratico: come strutturare una feature completa con AsyncNotifier, come usare la code generation per eliminare il boilerplate, e come scrivere test unitari e di widget con il ProviderContainer e gli override.
Conclusioni
Non esiste una risposta universale tra Riverpod e BLoC — esiste la risposta giusta per il tuo contesto specifico. Riverpod eccelle per produttivita, ergonomia e gestione dello stato asincrono. BLoC eccelle per audit trail, scalabilita dei team e compliance normativa.
Qualunque sia la scelta, entrambe le soluzioni producono app testabili, manutenibili e scalabili se usate correttamente. Il fattore determinante nella pratica non e tanto la libreria scelta quanto la disciplina con cui vengono applicati i principi architetturali che quella libreria promuove.







