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

Gestire lo stato in Flutter con Provider: guida pratica

Introduzione

La gestione dello stato è uno degli aspetti più importanti (e a volte confusi) nello sviluppo con Flutter. Quando la nostra app cresce, usare solo setState diventa difficile da mantenere e poco scalabile.

In questo tutorial impareremo a usare Provider, una delle soluzioni di state management più popolari e raccomandate dal team di Flutter. Costruiremo un semplice contatore e una lista della spesa per mostrare come condividere e aggiornare lo stato tra widget diversi senza passare manualmente i dati attraverso i costruttori.

Al termine avrai una base solida per applicare Provider nei tuoi progetti reali.

  1. 1

    Aggiungere la dipendenza Provider

    Per prima cosa aggiungiamo il pacchetto provider al nostro progetto. Apri il file pubspec.yaml e inseriscilo nella sezione dependencies, oppure usa il comando da terminale.

    Dopo aver aggiunto la dipendenza, esegui flutter pub get per scaricarla (il comando da terminale lo fa automaticamente).

    # Da terminale, nella cartella del progetto
    flutter pub add provider
    
    # Oppure manualmente in pubspec.yaml
    dependencies:
      flutter:
        sdk: flutter
      provider: ^6.1.2

    Risultato atteso

    Il pacchetto provider viene scaricato e diventa disponibile per l'import nel progetto.

  2. 2

    Creare il modello con ChangeNotifier

    Il cuore di Provider è la classe che contiene lo stato. Creiamo un file counter_model.dart con una classe che estende ChangeNotifier.

    Il metodo notifyListeners() è fondamentale: ogni volta che lo chiamiamo, tutti i widget in ascolto verranno ricostruiti con i nuovi dati.

    import 'package:flutter/foundation.dart';
    
    class CounterModel extends ChangeNotifier {
      int _count = 0;
    
      int get count => _count;
    
      void increment() {
        _count++;
        notifyListeners(); // notifica i widget in ascolto
      }
    
      void decrement() {
        if (_count > 0) {
          _count--;
          notifyListeners();
        }
      }
    
      void reset() {
        _count = 0;
        notifyListeners();
      }
    }

    Risultato atteso

    Hai una classe CounterModel che incapsula lo stato e notifica i cambiamenti.

  3. 3

    Registrare il Provider nell'albero dei widget

    Per rendere il modello accessibile ai widget, dobbiamo registrarlo in alto nell'albero. Usiamo ChangeNotifierProvider avvolgendo il widget radice (MyApp) nel main.

    In questo modo qualsiasi widget figlio potrà accedere all'istanza di CounterModel.

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'counter_model.dart';
    
    void main() {
      runApp(
        ChangeNotifierProvider(
          create: (context) => CounterModel(),
          child: const MyApp(),
        ),
      );
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Provider Demo',
          theme: ThemeData(primarySwatch: Colors.blue),
          home: const CounterPage(),
        );
      }
    }

    Risultato atteso

    L'istanza di CounterModel è ora disponibile per tutti i widget discendenti di MyApp.

  4. 4

    Leggere lo stato con Consumer

    Ora costruiamo la pagina che mostra il contatore. Usiamo il widget Consumer<CounterModel> per ascoltare i cambiamenti: solo la parte avvolta dal Consumer verrà ricostruita quando lo stato cambia, ottimizzando le prestazioni.

    In alternativa a Consumer, puoi usare context.watch<CounterModel>() direttamente nel metodo build.

    class CounterPage extends StatelessWidget {
      const CounterPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Contatore con Provider')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('Valore attuale:'),
                Consumer<CounterModel>(
                  builder: (context, model, child) {
                    return Text(
                      '${model.count}',
                      style: const TextStyle(fontSize: 48),
                    );
                  },
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    La pagina mostra il valore del contatore, che si aggiornerà automaticamente quando cambia.

  5. 5

    Modificare lo stato con context.read

    Per modificare lo stato senza ascoltarlo (ad esempio dentro un onPressed), usiamo context.read<CounterModel>(). È importante usare read e non watch nei callback, perché non vogliamo ricostruire il widget ma solo invocare un metodo.

    Aggiungiamo dei pulsanti per incrementare, decrementare e azzerare il contatore.

    floatingActionButton: Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        FloatingActionButton(
          heroTag: 'inc',
          onPressed: () => context.read<CounterModel>().increment(),
          child: const Icon(Icons.add),
        ),
        const SizedBox(height: 12),
        FloatingActionButton(
          heroTag: 'dec',
          onPressed: () => context.read<CounterModel>().decrement(),
          child: const Icon(Icons.remove),
        ),
        const SizedBox(height: 12),
        FloatingActionButton(
          heroTag: 'reset',
          onPressed: () => context.read<CounterModel>().reset(),
          child: const Icon(Icons.refresh),
        ),
      ],
    ),

    Risultato atteso

    Premendo i pulsanti il contatore aumenta, diminuisce o si azzera, e la UI si aggiorna in tempo reale.

  6. 6

    Ottimizzare con Selector e best practice

    Quando il modello contiene molti dati, ricostruire tutto a ogni notifyListeners() può essere inefficiente. Il widget Selector permette di ascoltare solo una parte specifica dello stato, ricostruendo la UI solo quando quel valore cambia.

    Best practice utili:

    • Usa context.read nei callback, context.watch o Consumer nel build.
    • Mantieni i modelli piccoli e focalizzati su una responsabilità.
    • Usa Selector per ottimizzare widget costosi.
    • Se ti servono più modelli, usa MultiProvider.
    // Esempio con Selector: ricostruisce solo se 'count' è pari
    Selector<CounterModel, bool>(
      selector: (context, model) => model.count.isEven,
      builder: (context, isEven, child) {
        return Text(isEven ? 'Pari' : 'Dispari');
      },
    )
    
    // Esempio con piu' provider
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CounterModel()),
        ChangeNotifierProvider(create: (_) => CartModel()),
      ],
      child: const MyApp(),
    )

    Risultato atteso

    La tua app gestisce lo stato in modo efficiente e scalabile, ricostruendo solo i widget necessari.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!