Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Flutter

Utilisation de ChangeNotifier dans Flutter

Un cours complet pour maîtriser Utilisation de ChangeNotifier dans Flutter.

0 chapitres 0 QCM Accès gratuit

Utilisation de ChangeNotifier dans Flutter

  1. Objectif de ChangeNotifier

    • Dans cet article, nous allons voir un exemple Provider Flutter complet en utilisant ChangeNotifier pour gérer l’état d’une application Flutter de manière performante et structurée.
    • Fournir un moyen simple de notifier les widgets d'un changement de données.
    • Séparer la logique métier (données et calculs) de l'interface utilisateur (UI).
    • Optimiser les performances en ne reconstruisant que les parties nécessaires de l'écran.
  2. Qu'est-ce que ChangeNotifier dans Flutter ?

    • Dans Flutter, ChangeNotifier est une classe simple qui permet à vos objets de notifier leurs écouteurs (widgets) lorsqu'un changement survient, évitant ainsi l'utilisation répétitive de setState() pour des mises à jour d'état plus efficaces, surtout avec le package Provider. Vous créez une classe qui étend ChangeNotifier, y placez votre état (variables), et appelez notifyListeners() à la fin de chaque méthode qui modifie cet état pour déclencher le rafraîchissement de l'interface utilisateur.
    • ChangeNotifier est une classe simple incluse dans le SDK Flutter qui permet de diffuser des notifications à ses "abonnés".
    • Lorsqu'une donnée change à l'intérieur d'une classe qui extends ou with ChangeNotifier, on appelle la méthode notifyListeners().
    • Cette méthode dit à tous les widgets qui écoutent ce modèle : "Hé, les données ont changé, redessinez-vous !".
  3. Comment fonctionne ChangeNotifier

    1. Création de la classe modèle (ViewModel) :
      • Importez package:flutter/foundation.dart.
      • Créez une classe qui hérite de ChangeNotifier (ex: class MonModele extends ChangeNotifier {}).
      • Définissez vos données (variables d'état) et les méthodes pour les modifier.
    2. Notification des changements :
      • Dans vos méthodes (ex: increment()), après avoir modifié une variable, appelez notifyListeners() pour alerter tous les widgets qui écoutent ce modèle.
    3. Fourniture du modèle (avec Provider) :
      • Utilisez ChangeNotifierProvider dans votre main.dart (ou au-dessus de la partie de l'UI qui en a besoin) pour rendre l'instance de votre modèle disponible dans l'arbre des widgets.
    4. Consommation du modèle (dans les Widgets) :
      • Utilisez le widget Consumer ou Provider.of(context) pour accéder au modèle. Le widget se reconstruira automatiquement lorsque notifyListeners() est appelé.
    5. Exemple simple
    6. Fichier model.dart
        
        import 'package:flutter/foundation.dart';
        
        class CounterModel extends ChangeNotifier {
          int _count = 0;
        
          int get count => _count;
        
          void increment() {
            _count++;
            notifyListeners(); // Notifie les écouteurs du changement
          }
        }
      Fichier main.dart
        
        // main.dart
        import 'package:flutter/material.dart';
        import 'package:provider/provider.dart';
        import 'model.dart';
        
        void main() {
          runApp(
            ChangeNotifierProvider( // Fournit le modèle à l'application
              create: (context) => CounterModel(),
              child: MyApp(),
            ),
          );
        }
        
        class MyApp extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: HomeScreen(),
            );
          }
        }
        
        class HomeScreen extends StatelessWidget {
          @Override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(title: Text('ChangeNotifier Demo')),
              body: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('Vous avez appuyé :'),
                    // Le Consumer se reconstruira seul
                    Consumer(
                      builder: (context, counter, child) {
                        return Text(
                          '${counter.count}',
                          style: Theme.of(context).textTheme.headline4,
                        );
                      },
                    ),
                  ],
                ),
              ),
              floatingActionButton: FloatingActionButton(
                onPressed: () {
                  // Accède au modèle et appelle la méthode
                  Provider.of(context, listen: false).increment();
                },
                child: Icon(Icons.add),
              ),
            );
          }
        }
  4. Les 3 piliers de ChangeNotifierProvider

    • Le Modèle (ChangeNotifier): La classe qui contient vos données et appelle notifyListeners().
    • Le Fournisseur (ChangeNotifierProvider): Le widget qui enveloppe votre application (ou une partie) pour rendre le modèle accessible.
    • Le Consommateur (Consumer/context.watch): Le widget qui lit les données et se reconstruit automatiquement.
  5. Mise en œuvre pratique : Un système de Favoris

    • Tout d’abord, Imaginons une application e-commerce ou catalogue dans laquelle l’utilisateur peut ajouter ou retirer des articles à une liste de favoris (❤️).
      1. Le Modèle : FavoriteProvider
        • Ce provider contient la logique métier des favoris :
        • il stocke la liste, permet d’ajouter/supprimer un élément
        • et notifie l’interface graphique à chaque changement.
        • import 'package:flutter/material.dart';
          
          class FavoriteProvider with ChangeNotifier {
          
            // Liste privée des favoris
            final List _favorites = [];
          
            // Getter public pour accéder aux favoris
            List get favorites => _favorites;
          
            // Ajout / suppression d'un favori
            void toggleFavorite(String item) {
              if (_favorites.contains(item)) {
                _favorites.remove(item);
              } else {
                _favorites.add(item);
              }
          
              // ? Notifie automatiquement tous les widgets abonnés
              notifyListeners();
            }
          
            // Vérifie si l'élément existe déjà
            bool isExist(String item) => _favorites.contains(item);
          }
          
      2. Injection du Provider (main.dart)
        • Le ChangeNotifierProvider est injecté à la racine de l’application, ce qui rend FavoriteProvider accessible à tous les widgets enfants.
        • import 'package:flutter/material.dart';
          import 'package:provider/provider.dart';
          
          void main() {
            runApp(
              ChangeNotifierProvider(
                create: (context) => FavoriteProvider(),
                child: const MyApp(),
              ),
            );
          }
          
      3. Consommation des données (UI)
        • Dans l’interface, on utilise context.watch<FavoriteProvider>() afin que la liste se reconstruise automatiquement lorsque l’utilisateur ajoute ou retire un favori.
        class ProductListScreen extends StatelessWidget {
        
          @override
          Widget build(BuildContext context) {
        
            // Récupération du provider
            final provider = context.watch<FavoriteProvider>();
        
            return ListView.builder(
              itemCount: 20,
              itemBuilder: (context, index) {
        
                String item = "Produit $index";
        
                return ListTile(
                  title: Text(item),
                  trailing: IconButton(
                    icon: Icon(
                      provider.isExist(item)
                          ? Icons.favorite
                          : Icons.favorite_border,
                      color: Colors.red,
                    ),
                    onPressed: () => provider.toggleFavorite(item),
                  ),
                );
              },
            );
          }
        }
        
  • Comparaison : context.watch vs context.read

  • Exemple de vie réelle :
  • Imaginons un magasin physique :
    • ? context.watch → Un écran d’affichage qui se met à jour automatiquement dès qu’un produit est ajouté ou retiré du panier.
    • ? context.read → Un bouton "Ajouter au panier" qui envoie simplement l’action, sans avoir besoin de se mettre à jour.
    • ? context.select → Un petit compteur qui affiche uniquement le nombre d’articles, sans se reconstruire pour le reste.
  • Objectif : comprendre quand écouter les changements et quand accéder aux données sans reconstruire l’interface.
      Méthode Rôle Lieu d'utilisation idéal
      context.watch<T>() Écoute les changements et reconstruit le widget. Dans la méthode build().
      context.read<T>() Accède au modèle sans écouter les changements. Dans les fonctions (ex: onPressed).
      context.select<T, R>() Écoute uniquement une propriété spécifique du modèle. Optimisation des gros modèles.

    Traduction en Flutter (exemple panier)

  • Nous utilisons un CartProvider qui contient une liste de produits et un nombre total d’articles.
  • 1️⃣ Utilisation de context.watch (UI réactive)

    Ici, le widget se reconstruit automatiquement lorsque le panier change.

    @override
    Widget build(BuildContext context) {
    
      // ? Écoute les changements
      final cart = context.watch();
    
      return Text(
        "Articles dans le panier : ${cart.totalItems}",
        style: TextStyle(fontSize: 18),
      );
    }
    
    2️⃣ Utilisation de context.read (action utilisateur)

    Ici, on déclenche une action sans écouter les changements.

    IconButton(
      icon: Icon(Icons.add_shopping_cart),
      onPressed: () {
        // ? Accès ponctuel sans reconstruction
        context.read().addItem(product);
      },
    );
    
    3️⃣ Utilisation de context.select (optimisation)

    Seule la valeur totalItems est écoutée. Le widget ne se reconstruit que si ce nombre change.

    @override
    Widget build(BuildContext context) {
    
      final total = context.select(
        (cart) => cart.totalItems,
      );
    
      return Text(
        "Total : $total",
        style: TextStyle(fontWeight: FontWeight.bold),
      );
    }
    
  • Conclusion :
    ✔️ watch → pour afficher des données dynamiques
    ✔️ read → pour déclencher des actions
    ✔️ select → pour optimiser les performances
  • Pour aller plus loin, consulte la documentation officielle : Package Provider sur pub.dev
  • Référence Flutter officielle : Gestion d’état avec Provider
  • Pourquoi choisir ChangeNotifier ?

    • Facilité d'apprentissage: Très proche de la programmation orientée objet classique.
    • Standard: C'est la recommandation officielle de l'équipe Flutter pour les applications de petite à moyenne taille.
    • Performance: Couplé avec Consumer ou Selector, il évite de reconstruire tout l'arbre de widgets.
  • Bonnes pratiques

    • Ne mettez pas de logique d'interface (UI) dans votre ChangeNotifier.
    • N'appelez pas notifyListeners() inutilement (ex: si la valeur n'a pas vraiment changé).
    • Utilisez MultiProvider dès que vous avez plus de deux providers pour éviter l'imbrication profonde.