Riverpod 3.0 versus BLoC 9: welk staatsbeheer moet ik kiezen in 2026?
De keuze voor het staatsmanagement in Flutter is een van de architectonische beslissingen meest impactvolle voor een project. Een slechte keuze wordt beloond met refactoring dure maanden later. In 2026 heeft de markt zich rond twee oplossingen geconsolideerd productieklaar: Rivierpod 3.0 e BLOC 9. Provider – de historische oplossing – en om als erfenis voor nieuwe projecten te worden beschouwd.
Er is geen universeel antwoord. Riverpod blinkt uit op het gebied van DX (Developer Ervaring), veiligheid tijdens het compileren en minder standaardteksten. BLoC behoudt een voorsprong in bedrijfscontexten met grote teams die een rigide structuur en audittrails vereisen van staten en een duidelijke scheiding die de testbaarheid afdwingt. Deze gids geeft je de instrumenten om te kiezen.
Wat je gaat leren
- Architectuur en filosofie van Riverpod 3.0 versus BLoC 9
- Beslissingsmatrix: welke u moet gebruiken op basis van uw context
- Riverpod: AsyncNotifier, code genereren, testen met overschrijven
- BLoC: Cubit versus BLoC, verzegelde klassen, hydratatie
- Migratie van Provider naar Riverpod: patronen en valkuilen
- Prestatievergelijking: granulariteit opnieuw opbouwen
- Gemeenschap, ecosysteem, volwassen geworden in 2026
Riverpod 3.0: filosofie en architectuur
Riverpod is ontworpen om de structurele problemen van Provider op te lossen: afhankelijkheid van Flutter-context, onvermogen om toegang te krijgen tot externe providers uit de widgetboom, runtimefouten voor providers niet gevonden. Riverpod lost het op deze problemen op te lossen door providers wereldwijd te verplaatsen met veiligheid bij het compileren.
// pubspec.yaml: dipendenze Riverpod 3.0
dependencies:
flutter_riverpod: ^3.0.0
riverpod_annotation: ^3.0.0
dev_dependencies:
riverpod_generator: ^3.0.0
build_runner: ^2.4.0
riverpod_lint: ^2.0.0
// Struttura base: provider senza context
// Senza code generation (verboso ma esplicito)
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider semplice: valore computato
final userNameProvider = Provider<String>((ref) => 'Federico');
// StateProvider: stato semplice (counter, booleano, stringa)
final counterProvider = StateProvider<int>((ref) => 0);
// AsyncNotifierProvider: stato asincrono con loading/error/data
class UserNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async {
// build() e chiamato all'inizializzazione e ai rebuild
return ref.watch(userRepositoryProvider).fetchCurrentUser();
}
Future<void> updateProfile(String name) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final updated = await ref.read(userRepositoryProvider).update(name);
return updated;
});
}
}
final userProvider = AsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);
// Riverpod con code generation: zero boilerplate
// user_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user_provider.g.dart'; // generato da build_runner
@riverpod
class UserController extends _$UserController {
@override
Future<User> build() async {
// ref disponibile come campo, nessun parametro al costruttore
return ref.watch(userRepositoryProvider).fetchCurrentUser();
}
Future<void> updateProfile(String name) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return ref.read(userRepositoryProvider).update(name);
});
}
}
// @riverpod genera automaticamente userControllerProvider
// Equivalente a:
// final userControllerProvider = AsyncNotifierProvider<UserController, User>(
// UserController.new
// );
// Widget consumer: accede allo stato senza context.read()
class UserProfileWidget extends ConsumerWidget {
const UserProfileWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// ref.watch: si ricostruisce quando lo stato cambia
final userAsync = ref.watch(userControllerProvider);
return userAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error.toString()),
data: (user) => Text(user.name),
);
}
}
BLoC 9: Filosofie en Architectuur
BLoC (Business Logic Component) scheidt de gebruikersinterface strikt van de bedrijfslogica via unidirectionele stromen: de gebruikersinterface verzendt gebeurtenissen naar de BLoC, de BLoC zendt toestanden uit, de gebruikersinterface herbouwt zichzelf als reactie op toestanden. Deze duidelijke scheiding is waar het om gaat van kracht in grote teams waar testbaarheid en voorspelbaarheid van cruciaal belang zijn.
// pubspec.yaml: dipendenze BLoC 9
dependencies:
flutter_bloc: ^9.0.0
equatable: ^2.0.5 // Per comparazione stati
dev_dependencies:
bloc_test: ^9.0.0 // Testing utilities
// Cubit: versione semplificata di BLoC (senza eventi espliciti)
// Usalo quando lo stato ha transizioni semplici
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0); // stato iniziale
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
void reset() => emit(0);
}
// BLoC completo: con eventi e stati espliciti
// Sealed classes Dart 3.x per stati type-safe
// events
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
final String email, password;
LoginRequested(this.email, this.password);
}
class LogoutRequested extends AuthEvent {}
class TokenRefreshRequested extends AuthEvent {}
// states con sealed class
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
AuthAuthenticated(this.user);
}
class AuthUnauthenticated extends AuthState {}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
}
// BLoC
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository _repo;
AuthBloc(this._repo) : super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
on<LogoutRequested>(_onLogoutRequested);
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
final user = await _repo.login(event.email, event.password);
emit(AuthAuthenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
}
}
Future<void> _onLogoutRequested(
LogoutRequested event,
Emitter<AuthState> emit,
) async {
await _repo.logout();
emit(AuthUnauthenticated());
}
}
Beslissingsmatrix: Riverpod versus BLoC
| Criterium | Rivierpod 3.0 | BLOC 9 |
|---|---|---|
| Ketelplaat | Laag (codegen) | Hoog (gebeurtenissen + statussen) |
| Veiligheid tijdens het compileren | Uitstekend | Goed (verzegelde klassen) |
| Getest | Uitstekend (overschrijven) | Uitstekend (blok_test) |
| Stijve structuur | Flexibele | Zeer stijf |
| Geweldige ploegen | Goed | Uitstekend |
| Leercurve | Gemiddeld | Hoog |
| Asynchrone afhandeling | Uitstekend (AsyncValue) | Goed (handmatig) |
| Volharding van de staat | Geïntegreerd (Riverpod-opslag) | gehydrateerd_blok |
Wanneer moet u voor Riverpod kiezen?
- Klein-middelgroot team (1-5 ontwikkelaars)
- Startend of snel evoluerend product waarbij het terugdringen van de standaard de ontwikkeling versnelt
- Veel asynchrone verzoeken waarbij AsyncValue het laden/fouten/gegevens vereenvoudigt
- Migratie van provider (familie-API)
- Wanneer u toegang wilt krijgen tot providers buiten de widgetboom (services, achtergrondtaken)
Wanneer kiest u voor BLoC?
- Enterprise-team (6+ ontwikkelaars) met strenge codebeoordelingen
- Gereguleerde sectoren (fintech, gezondheidszorg) waarvoor staatsaudits nodig zijn
- Project waarbij de scheiding tussen gebruikersinterface en logica architectonisch moet worden afgedwongen
- Team met Redux/NgRx-ervaring: filosofie en dergelijke
- Als je wilt dat elke staatstransitie expliciet en traceerbaar is
Migratie van leverancier naar Riverpod
// Migrazione graduale: Provider -> Riverpod
// PRIMA: Provider (legacy)
// Provider: dipende dal context
final userProvider = ChangeNotifierProvider<UserModel>((ctx) => UserModel());
// Nel widget:
// final user = Provider.of<UserModel>(context);
// final user = context.watch<UserModel>();
// DOPO: Riverpod equivalente
// Nessuna dipendenza dal context
class UserNotifier extends Notifier<UserState> {
@override
UserState build() => UserState.initial();
void setName(String name) => state = state.copyWith(name: name);
}
final userNotifierProvider = NotifierProvider<UserNotifier, UserState>(
UserNotifier.new
);
// Strategia di migrazione graduale:
// 1. Aggiungi flutter_riverpod al progetto senza rimuovere Provider
// 2. Avvolgi l'app con ProviderScope (necessario per Riverpod)
// ProviderScope puo coesistere con MultiProvider
// 3. Migra un provider alla volta partendo dai leaf (quelli senza dipendenze)
// 4. Usa ref.read(oldProviderViaAdapter) come bridge temporaneo
// 5. Rimuovi Provider quando tutti i consumer sono migrati
Conclusies
In 2026 zijn beide oplossingen volwassen en beproefd. De keuze hangt ervan af uit context: Voor de meeste projecten biedt Riverpod 3.0 het beste DX met minder code om te schrijven en te onderhouden. Voor ondernemingsteams met vereisten Dankzij de audit trails en de rigide structuur blijft BLoC onovertroffen. De belangrijke e kies en wees consistent: vermijd het combineren van de twee patronen in dezelfde project zonder duidelijke strategie.







