Utilisation de ChangeNotifier dans Flutter
Utilisation de ChangeNotifier dans Flutter
-
Objectif de ChangeNotifier
- 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 ! ».
-
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é. -
Exemple simple
-
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. -
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.
-
Injection du Provider (main.dart)
- Le
ChangeNotifierProviderest injecté à la racine de l’application, ce qui rendFavoriteProvideraccessible à tous les widgets enfants. -
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.
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),
),
);
}
}
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);
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => FavoriteProvider(),
child: const MyApp(),
),
);
}
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
- 👀 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 produitset 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
Package Provider sur pub.dev
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
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.
