Riverpod — Gestione Compilata delle Dipendenze per Flutter

Autore: IT Sectr Pubblicato: 2026-02-19 Tempo di lettura: 7 min

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

  • ProviderRef — un oggetto per accedere ad altri provider all'interno di un provider
  • AsyncValue — un wrapper per dati asincroni con stati loading/error/data
  • Notifier — una classe per stato mutabile con metodi di mutazione
  • ProviderScope — il widget radice che gestisce tutti i provider
  • Code Generation — annotazioni @riverpod per la generazione automatica di provider

Cos'è Riverpod?

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.

Tipi di Provider

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).

Dart
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 e lavoro con l'asincronia

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.

Dart
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 e @riverpod

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.

Dart
@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.

Riverpod vs Provider

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.

CaratteristicaProviderRiverpod
Dipende da BuildContextNo
Controllo in compilazioneNoSì (tramite @riverpod)
ProviderNotFoundExceptionRuntimeImpossibile
AsincroniaManualeAsyncValue (integrato)
TestWrapper in ProviderProviderScope.overrideWith
CachingNoAutomatico + 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

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.

Dart
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

In cosa Riverpod è diverso da BLoC?

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.

Cos'è autodispose in Riverpod?

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.

Come funziona ref.invalidate?

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.

Si può usare Riverpod senza generazione del codice?

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.

Cosa sono i provider Family?

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

  • Riverpod — un gestore di stato compilato, successore di Provider senza ProviderNotFoundException
  • ProviderRef — un sostituto di BuildContext per accedere ai provider all'interno di altri provider
  • AsyncValue — una classe sealed con stati loading/error/data per dati asincroni
  • Generazione codice @riverpod — inferenza automatica dei tipi e factory di provider
  • ProviderScope.overrideWith — test isolati senza mock e contenitori DI
  • Family — provider parametrizzati con caching individuale
  • autodispose e keepAlive — gestione automatica del ciclo di vita dei provider

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.

Discuti il progetto

Leggi anche