Utilisation de ChangeNotifier dans Flutter
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.
Qu'est-ce que ChangeNotifier dans Flutter ?
- Dans Flutter,
ChangeNotifierest une classe simple qui permet à vos objets de notifier leurs écouteurs (widgets) lorsqu'un changement survient, évitant ainsi l'utilisation répétitive desetState()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. ChangeNotifierest 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
extendsouwithChangeNotifier, on appelle la méthodenotifyListeners(). - Cette méthode dit à tous les widgets qui écoutent ce modèle : "Hé, les données ont changé, redessinez-vous !".
- Dans Flutter,
Comment fonctionne ChangeNotifier
-
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.
-
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.
-
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.
-
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é.
- Utilisez le widget Consumer ou Provider.of
-
Exemple simple
Fichier
model.dartimport '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), ), ); } } -
Les 3 piliers de ChangeNotifierProvider
- Le Modèle (
ChangeNotifier): La classe qui contient vos données et appellenotifyListeners(). - 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.
- Le Modèle (
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 (❤️).
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); } Injection du Provider (main.dart)
- Le
ChangeNotifierProviderest injecté à la racine de l’application, ce qui rendFavoriteProvideraccessible à tous les widgets enfants.
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp( ChangeNotifierProvider( create: (context) => FavoriteProvider(), child: const MyApp(), ), ); }- Le
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), ), ); }, ); } }- Dans l’interface, on utilise
-
Tout d’abord, Imaginons une application e-commerce ou catalogue dans laquelle l’utilisateur peut ajouter ou retirer des articles à une liste de favoris (❤️).
Comparaison : context.watch vs context.read
- ? 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.
| 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)
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),
);
}
✔️
watch → pour afficher des données dynamiques✔️
read → pour déclencher des actions✔️
select → pour optimiser les performances
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
ConsumerouSelector, 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.