Riverpod — un gestore di stato e dipendenze compilato per Flutter, creato da Remi Rousselet nel 2021 come successore di Provider. Riverpod risolve i problemi fondamentali di Provider: mancanza di controllo in fase di compilazione, dipendenza da BuildContext e complessità con ProviderNotFoundException. Secondo pub.dev, il pacchetto ha più di 5 mila like e sta attivamente sostituendo Provider nei nuovi progetti.
Punti Chiave
Riverpod è una libreria di gestione dello stato e iniezione delle dipendenze per Flutter che compila le descrizioni dei provider in codice Dart sicuro. A differenza di Provider, i provider di Riverpod non sono legati a BuildContext: vengono creati globalmente o in ProviderScope e sono accessibili da qualsiasi luogo. Il compilatore verifica tipi, dipendenze e l'integrità del grafo dei provider in fase di compilazione, eliminando errori a runtime come ProviderNotFoundException.
Riverpod utilizza il modello override per i test: ogni provider può essere sovrascritto tramite ProviderScope.overrideWith senza creare sottoclassi o mockare interfacce. Ciò rende i test isolati: ogni test ottiene la propria copia del grafo delle dipendenze che è completamente controllata.
Secondo il Flutter Community Survey 2025, Riverpod occupa il terzo posto in popolarità dopo Provider e BLoC. Tuttavia, Riverpod è il pacchetto in più rapida crescita: +120% di installazioni nel 2024. Motivi principali: sicurezza in fase di compilazione, nessun ProviderNotFoundException, supporto asincrono integrato tramite AsyncValue.
Riverpod fornisce 8 tipi di provider, ciascuno per uno scenario specifico: Provider (costante/servizio), StateProvider (stato primitivo), StateNotifierProvider (logica complessa con StateNotifier), ChangeNotifierProvider (per migrazione da Provider), FutureProvider (dati asincroni, una volta), StreamProvider (flusso reattivo), NotifierProvider (nuova API, Flutter 3.10+) e AsyncNotifierProvider (Notifier asincrono).
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}ProviderRef — un oggetto passato a ogni provider per accedere ad altri provider. ref.watch — sottoscrizione ai cambiamenti, ref.read — lettura una tantum, ref.invalidate — reset della cache. ProviderRef sostituisce BuildContext di Provider: qualsiasi provider può leggere altri provider senza accedere all'albero dei widget. Ciò consente di costruire un grafo di dipendenze al di fuori del livello UI.
ProviderScope — il widget radice, obbligatorio per il funzionamento di Riverpod. ProviderScope memorizza tutti i provider, gestisce il loro ciclo di vita e memorizza nella cache i valori. Senza ProviderScope l'app si bloccherà con ProviderNotFoundException. ProviderScope può essere annidato — un ambito annidato sovrascrive i provider genitori, utilizzato per test e isolamento delle funzionalità.
AsyncValue — una classe sealed di Riverpod per rappresentare lo stato asincrono. AsyncValue ha tre varianti: AsyncData (dati riusciti), AsyncError (errore), AsyncLoading (caricamento). Invece di passare manualmente tra loading/error/data, ogni FutureProvider o StreamProvider restituisce automaticamente AsyncValue, e il widget gestisce tutti e tre gli stati tramite ref.watch.
final userProvider = FutureProvider((ref) async {
final api = ref.watch(apiProvider);
return await api.fetchUser();
});
class UserScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => UserWidget(user),
error: (e, _) => ErrorWidget(e.toString()),
loading: () => CircularProgressIndicator(),
);
}
}AsyncValue.when — un metodo per il pattern-matching di tutti e tre gli stati. Il compilatore verifica che tutti e tre i casi siano gestiti — se dimentichi loading o error, il codice non compilerà. AsyncValue.whenData — solo per data (se loading/error non sono necessari). AsyncValue.guard — un wrapper su try-catch per convertire eccezioni in AsyncError. keepAlive — un flag che impedisce la distruzione della cache del provider quando esce dall'ambito.
Generazione del codice — una caratteristica chiave di Riverpod 2.0+. L'annotazione @riverpod su una funzione genera automaticamente un provider con il tipo corretto, supporto al refactoring e autocompletamento. La generazione del codice utilizza riverpod_generator e build_runner. Lo sviluppatore scrive una funzione pura, e tutto il resto — tipi, classi, costruttori factory — viene generato automaticamente.
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello World';
}
// Generato: final helloWorldProvider = Provider((ref) => 'Hello World');
@riverpod
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}Notifier — la nuova API per lo stato mutabile con generazione del codice. Notifier è una classe con un metodo build() e metodi di mutazione dello stato. A differenza di StateNotifier, Notifier non richiede una classe di stato separata e fornisce accesso diretto a state tramite getter/setter. Riverpod genera automaticamente un NotifierProvider per ogni classe Notifier annotata con @riverpod.
build_runner: la generazione del codice viene eseguita con il comando dart run build_runner build. I file generati hanno il suffisso .g.dart e vengono importati nel codice sorgente. Quando le annotazioni o i tipi di provider cambiano, la generazione del codice deve essere rieseguita. Riverpod 2.x raccomanda la generazione del codice per tutti i nuovi progetti — la creazione manuale di provider sta diventando obsoleta.
Differenze principali tra Riverpod e Provider: indipendenza da BuildContext, sicurezza in fase di compilazione, supporto asincrono integrato, caching automatico e test tramite override. Provider richiede BuildContext per accedere allo stato (context.watch, context.read), Riverpod utilizza WidgetRef e provider dichiarati globalmente.
| Caratteristica | Provider | Riverpod |
|---|---|---|
| Dipende da BuildContext | Sì | No |
| Controllo in compilazione | No | Sì (tramite @riverpod) |
| ProviderNotFoundException | Runtime | Impossibile |
| Asincronia | Manuale | AsyncValue (integrato) |
| Test | Wrapper in Provider | ProviderScope.overrideWith |
| Caching | No | Automatico + keepAlive |
Migrazione da Provider: Riverpod supporta ChangeNotifierProvider.adaptive per utilizzare ChangeNotifier esistenti senza riscrittura. Migrazione graduale: prima le nuove funzionalità vengono scritte con Riverpod, poi le vecchie istanze di Provider vengono sostituite con provider Riverpod tramite un adattatore. Entrambi i pacchetti possono coesistere in un progetto, consentendo la migrazione senza bloccare lo sviluppo.
Testare Riverpod si basa su ProviderScope.overrideWith. Ogni provider viene sovrascritto all'interno di un ProviderScope di test senza mock o contenitori DI. ProviderContainer — un ambiente isolato per test senza Flutter (Dart puro), che consente di testare i provider senza rendering di widget.
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
void main() {
test('Counter increments correctly', () {
final container = ProviderContainer();
container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider), 1);
});
testWidgets('UI updates on increment', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [counterProvider.overrideWithValue(5)],
child: CounterScreen(),
),
);
expect(find.text('5'), findsOneWidget);
});
}ProviderContainer — senza Flutter. Utilizza ProviderContainer per test unitari di provider senza widget. overrideWithValue — sostituire un provider con un valore specifico. overrideWith — sostituire con una factory di provider (per mockare servizi). autodispose — nei test verifica che il provider venga distrutto quando esce dall'ambito utilizzando container.dispose().
Domande frequenti
Riverpod è una libreria di gestione dello stato con provider globali, AsyncValue e generazione del codice. BLoC è un pattern architetturale con Event → Stream → State. Riverpod è più facile da imparare e ha una migliore DX grazie alle annotazioni @riverpod. BLoC fornisce un rigoroso isolamento della logica di business e tracciamento degli Event tramite BlocObserver. La scelta dipende dal paradigma del progetto: Riverpod è più vicino a Provider, BLoC — ai flussi reattivi.
Autodispose è un meccanismo di distruzione automatica di un provider quando nessuno vi è sottoscritto. Per impostazione predefinita, tutti i provider Riverpod fanno autodispose: quando un widget esce dall'albero, il provider viene rimosso dalla memoria. keepAlive — un flag che disabilita autodispose per i provider che devono vivere sempre (client API, repository, impostazioni). Questo previene perdite di memoria — i provider inutilizzati vengono automaticamente distrutti.
ref.invalidate — un metodo che resetta forzatamente la cache del provider. Dopo invalidate, il provider viene ricreato alla lettura successiva: FutureProvider riesegue la funzione asincrona, StreamProvider si riabbona al flusso. Usa invalidate per forzare l'aggiornamento dei dati (pull-to-refresh, cambio utente). ref.refresh — una combinazione di invalidate + lettura: resetta e legge immediatamente il nuovo valore in una singola operazione.
Sì. Riverpod 1.x funziona solo senza generazione del codice — i provider vengono creati manualmente usando Provider(), StateNotifierProvider(), FutureProvider(), ecc. Riverpod 2.x supporta entrambi gli approcci. Senza generazione del codice c'è più boilerplate ma nessuna dipendenza da build_runner e dart run build_runner build. Per progetti piccoli (fino a 30 provider), la creazione manuale è giustificata; per quelli grandi, la generazione del codice è obbligatoria.
Family — un modificatore di provider che accetta un parametro esterno. Ad esempio, userProvider(123) — un provider che carica un utente con ID 123. I provider Family memorizzano nella cache il risultato per ogni parametro unico separatamente. Usa Family per elenchi di elementi in cui ogni elemento viene caricato per ID. Il modificatore Family è disponibile per tutti i tipi di provider: Provider.family, FutureProvider.family, StreamProvider.family.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche