Gestire lo stato in Flutter con Riverpod: guida pratica
GuideIntermedio35 min Flutter 3.x

Gestire lo stato in Flutter con Riverpod: guida pratica

Riverpod è una libreria di gestione dello stato moderna, evoluzione spirituale di Provider, creata dallo stesso autore. A differenza di Provider, Riverpod non dipende dal BuildContext, è completamente type-safe e gestisce in modo elegante stati asincroni e dipendenze tra provider.

In questo tutorial costruiremo un piccolo contatore e poi una schermata che carica dati in modo asincrono, utilizzando i generatori di codice di flutter_riverpod e riverpod_annotation. Al termine saprai dichiarare provider, leggere e modificare lo stato e gestire i casi di caricamento ed errore.

  1. 1

    Installare le dipendenze

    Aggiungiamo flutter_riverpod per il runtime e, opzionalmente, riverpod_annotation insieme ai generatori di codice per uno stile più conciso e moderno.

    Esegui questi comandi nella root del progetto:

    flutter pub add flutter_riverpod riverpod_annotation
    flutter pub add dev:riverpod_generator dev:build_runner dev:custom_lint dev:riverpod_lint

    Risultato atteso

    Le dipendenze vengono aggiunte al file pubspec.yaml senza errori.

  2. 2

    Avvolgere l'app con ProviderScope

    Tutti i provider di Riverpod vengono memorizzati in un ProviderScope. Deve essere il widget radice dell'applicazione: senza di esso i provider non funzionano.

    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    
    void main() {
      runApp(
        const ProviderScope(
          child: MyApp(),
        ),
      );
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CounterPage(),
        );
      }
    }

    Risultato atteso

    L'app si avvia normalmente: ora l'intero albero dei widget ha accesso ai provider.

  3. 3

    Creare un Notifier per il contatore

    Usiamo l'annotazione @riverpod per generare un NotifierProvider. Il metodo build restituisce lo stato iniziale, mentre i metodi pubblici permettono di modificarlo aggiornando state.

    Salva il file come counter.dart. La parte part 'counter.g.dart'; è necessaria al generatore di codice.

    import 'package:riverpod_annotation/riverpod_annotation.dart';
    
    part 'counter.g.dart';
    
    @riverpod
    class Counter extends _$Counter {
      @override
      int build() => 0; // stato iniziale
    
      void increment() => state = state + 1;
      void decrement() => state = state - 1;
      void reset() => state = 0;
    }

    Risultato atteso

    Il file è pronto, ma servirà generare il codice nel passo successivo.

  4. 4

    Generare il codice con build_runner

    Il generatore crea il file *.g.dart con il provider counterProvider. Lancia il comando una volta, oppure tienilo in watch durante lo sviluppo per la rigenerazione automatica.

    dart run build_runner watch -d

    Risultato atteso

    Viene creato il file counter.g.dart e l'identificatore counterProvider diventa disponibile.

  5. 5

    Consumare lo stato in un widget

    Per leggere i provider all'interno di un widget usiamo ConsumerWidget (o ConsumerStatefulWidget), che fornisce un oggetto WidgetRef.

    • ref.watch(provider) osserva lo stato e ricostruisce il widget quando cambia.
    • ref.read(provider.notifier) ottiene il Notifier per invocare i metodi senza ricostruire.
    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    import 'counter.dart';
    
    class CounterPage extends ConsumerWidget {
      const CounterPage({super.key});
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final count = ref.watch(counterProvider);
    
        return Scaffold(
          appBar: AppBar(title: const Text('Riverpod Counter')),
          body: Center(
            child: Text(
              'Valore: $count',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ),
          floatingActionButton: Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              FloatingActionButton(
                onPressed: () => ref.read(counterProvider.notifier).decrement(),
                child: const Icon(Icons.remove),
              ),
              const SizedBox(width: 12),
              FloatingActionButton(
                onPressed: () => ref.read(counterProvider.notifier).increment(),
                child: const Icon(Icons.add),
              ),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Premendo i pulsanti + e - il valore mostrato a schermo si aggiorna in tempo reale.

  6. 6

    Gestire stati asincroni con AsyncValue

    Riverpod brilla con i dati asincroni. Un provider che restituisce un Future espone uno stato di tipo AsyncValue, che incapsula i tre casi: caricamento, dato disponibile ed errore.

    Definiamo un provider asincrono che simula una chiamata di rete (ricorda di rigenerare il codice).

    import 'package:riverpod_annotation/riverpod_annotation.dart';
    
    part 'user.g.dart';
    
    @riverpod
    Future<String> userName(UserNameRef ref) async {
      await Future.delayed(const Duration(seconds: 2));
      return 'Mario Rossi';
    }

    Risultato atteso

    Dopo la rigenerazione è disponibile userNameProvider di tipo FutureProvider.

  7. 7

    Mostrare caricamento, dato ed errore con .when

    Nel widget usiamo ref.watch sul provider asincrono e il metodo .when di AsyncValue per gestire in modo esaustivo tutti gli stati, evitando dimenticanze.

    class UserTile extends ConsumerWidget {
      const UserTile({super.key});
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final userAsync = ref.watch(userNameProvider);
    
        return userAsync.when(
          loading: () => const CircularProgressIndicator(),
          error: (err, stack) => Text('Errore: $err'),
          data: (name) => Text('Benvenuto, $name!'),
        );
      }
    }

    Risultato atteso

    Per 2 secondi compare uno spinner, poi appare il messaggio di benvenuto; in caso di eccezione viene mostrato l'errore.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!