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

Flutter Provider

Un cours complet pour maîtriser Flutter Provider.

0 chapitres 0 QCM Accès gratuit

Flutter Provider

Flutter Provider

  1. Objectif

    • Simplifier la gestion d’état dans une application Flutter.
    • Partager des données entre widgets sans avoir besoin de passer des paramètres manuellement (lifting state up).
    • Assurer une interface réactive, toujours synchronisée avec les données.
    • Proposer une approche claire, simple et évolutive par rapport à setState().
  2. Qu'est-ce que "l'état" (State) ?

    • Pour bien comprendre pourquoi le package Provider est si populaire, il faut d'abord définir ce qu'est "l'état" (ou State) dans le monde de Flutter.
    • Dans Flutter, l'état représente toutes les données qui peuvent changer au cours de la vie de votre application et qui influencent ce que l'utilisateur voit à l'écran.
    • On distingue généralement deux types d'états :
      • L'état local (Ephemeral State): Des données qui ne concernent qu'un seul widget (ex: l'onglet actuellement sélectionné dans une barre de navigation, ou le texte en cours de saisie dans un champ). On utilise souvent un simple setState().
      • L'état partagé (App State): Des données qui doivent être accessibles dans plusieurs parties de l'application (ex: les infos de l'utilisateur connecté, le contenu d'un panier d'achat, les préférences de thème). C'est ici que Provider intervient.
  3. Définition du package Provider

    • Flutter Provider est une solution de gestion d'état pour les applications Flutter. Elle permet de gérer facilement l'état de l'application et de le partager entre plusieurs widgets, facilitant ainsi l'accès aux données là où elles sont nécessaires.
    • Vous stockez les données dans un emplacement central appelé provider et y accédez depuis n'importe quel widget de votre application.
    • Dès que les données changent, le provider les met à jour, garantissant la cohérence et la réactivité de l’UI.
    • Les principaux avantages :
      • Centralisation de l’état
      • Découplage des widgets
      • Optimisation des performances (rebuild ciblé)
      flutter-provider
  4. Les types de Provider les plus courants

    1. Provider (de base)
        Utilisation
        • Fournit un objet simple (service ou donnée en lecture seule).
        • Ne notifie pas automatiquement les changements.
      • Description :
        • Utilisé lorsque les données ne changent pas pendant l’exécution (configuration, utilisateur connecté, service).
      • Exemple de vie réelle :
        • Une carte d’identité : le nom est lu partout mais ne change pas.
      • Exemple Flutter :
      // Création
      Provider<String>(
        create: (context) => "Mohamed RAYES",
        child: MonWidget(),
      );
      
      // Utilisation
      Text(Provider.of<String>(context));
      
    2. ChangeNotifierProvider <T>
        Utilisation
        • Utilisé pour des données modifiables.
        • Notifie automatiquement les widgets quand l’état change.
      • Description :
        • Basé sur la classe ChangeNotifier et la méthode notifyListeners().
      • Exemple de vie réelle :
        • Un compteur de personnes dans une salle : chaque entrée modifie le total.
      • Cas typiques : panier, compteur, favoris, todo-list.
      • class Counter with ChangeNotifier {
          int value = 0;
        
          void increment() {
            value++;
            notifyListeners();
          }
        }
        
        ChangeNotifierProvider(
          create: (_) => Counter(),
          child: MyApp(),
        );
        
        Utilisation avec Consumer :
        Consumer<Counter>(
          builder: (context, counter, child) {
            return Text('${counter.value}');
          },
        );
    3. StreamProvider <T>
        Utilisation
        • Expose un Stream de données en temps réel.
      • Description :
        • Les widgets se mettent à jour automatiquement dès qu’une nouvelle donnée arrive dans le flux.
      • Exemple de vie réelle :
        • Un tableau d’affichage dans une gare : les horaires changent en direct.
        StreamProvider<int>(
          create: (_) => Stream.periodic(
            Duration(seconds: 1),
            (i) => i,
          ),
          initialData: 0,
          child: MyApp(),
        );
        
    4. FutureProvider <T>
        Utilisation
        • Expose un Future pour des données asynchrones.
      • Description :
        • Les données sont chargées une seule fois (appel API, chargement initial).
      • Exemple de vie réelle :
        • Commander un repas : on attend, puis on reçoit le résultat.
        FutureProvider<User>(
          create: (_) async => fetchUser(),
          initialData: User.empty(),
          child: MyApp(),
        );
        
    5. MultiProvider
        Utilisation
        • Regroupe plusieurs providers en un seul point.
          • Description :
            • Permet de garder un code propre dans les applications complexes.
          • Exemple de vie réelle :
            • Une multiprise : plusieurs appareils branchés sur une seule prise.
            MultiProvider(
              providers: [
                Provider(create: (_) => MyService()),
                ChangeNotifierProvider(create: (_) => Counter()),
                StreamProvider(
                  create: (_) => myStream(),
                  initialData: 0,
                ),
              ],
              child: MyApp(),
            );
            
        • NotifierProvider <T extends Notifier>
            Utilisation
            • Gestion moderne de l’état.
            • Ne nécessite pas notifyListeners().
          • Description :
            • À chaque modification, un nouvel état est produit automatiquement.
          • Exemple de vie réelle :
            • Un tableau de score : l’écran affiche directement la nouvelle valeur.
            class Counter extends Notifier<int> {
              @override
              int build() => 0;
            
              void increment() => state++;
            }
            
            final counterProvider =
              NotifierProvider<Counter, int>(() => Counter());
            
            Utilisation :
            final count = context.watch(counterProvider);
            
        • AsyncNotifierProvider <T>
            Utilisation
            • Gestion asynchrone moderne (Future / Stream).
          • Description :
            • Gère automatiquement les états : chargement, erreur et données.
          • Exemple de vie réelle :
            • Une commande en ligne : en cours → erreur ou succès.
            class UserNotifier extends AsyncNotifier<User> {
              @override
              Future<User> build() async {
                return await fetchUser();
              }
            }
            
            final userProvider =
              AsyncNotifierProvider<UserNotifier, User>(
                () => UserNotifier(),
              );
            
            Utilisation :
            final user = context.watch(userProvider);
            
            if (user is AsyncLoading) {
              return CircularProgressIndicator();
            } else if (user is AsyncError) {
              return Text('Erreur');
            } else {
              return Text(user.value.name);
            }
            
  5. Comment utiliser un package provider Flutter ?

    • Étape 1 : ajouter la dépendance dans pubspec.yaml.
    • Étape 2 : créer un modèle de données.
    • Étape 3 : envelopper l’application dans un Provider.
    • Étape 4 : consommer les données avec context.watch, Consumer ou Selector.
  6. Utilisation directe de .addListener

    • Vous pouvez écouter directement les changements d’un ChangeNotifier.
    final counter = Counter();
    counter.addListener(() {
      print("Nouveau compteur: ${counter.value}");
    });
    
  7. Moins utilisé, mais utile pour du code métier hors widgets.
  8. Comparaison des principaux Providers

  9. Provider Type d’état Méthode de mise à jour Cas d’utilisation
    ChangeNotifierProvider Mutable (objet modifié) notifyListeners() Compteur, panier, todo-list
    (UI dépendante de changements fréquents)
    Exemple :
    class Counter with ChangeNotifier {
      int value = 0;
    
      void increment() {
        value++;
        notifyListeners();
      }
    }
    NotifierProvider Immuable (nouvel état) Réaffectation de state Gestion moderne et performante
    (états synchrones)
    Exemple :
    class Counter extends Notifier<int> {
      @override
      int build() => 0;
    
      void increment() => state++;
    }
    AsyncNotifierProvider Asynchrone (Future / Stream) Résultat asynchrone API REST, Firebase, base de données distante
    Exemple :
    class UserNotifier extends AsyncNotifier<User> {
      @override
      Future<User> build() async {
        return fetchUser();
      }
    }
  10. Bonnes pratiques

    • Préférer context.read pour déclencher une action, et context.watch pour reconstruire l’UI.
    • Découper les providers pour garder des responsabilités claires.
    • Utiliser Selector pour éviter des rebuilds inutiles.
    • Pour les projets modernes, envisager NotifierProvider / AsyncNotifierProvider.
  11. Exemple de vie réelle : Panier d’achat (Shopping Cart)

    1. Situation réelle
      • Dans un magasin :
      • Un client ajoute des produits à son panier
      • Le nombre d’articles change
      • Le total du panier se met à jour automatiquement
      • Plusieurs écrans doivent accéder au même panier :
        • Liste des produits
        • Icône du panier
        • Page de paiement
      • C’est exactement un problème de gestion d’état partagé
    2. Pourquoi utiliser Provider ici ?
        Sans Provider :
        • Il faudrait passer le panier de widget en widget (prop drilling)
        • Le code devient complexe et difficile à maintenir
        Avec Provider :
        • Le panier est centralisé
        • Tous les widgets sont synchronisés automatiquement
        • Le code reste simple et propre
      • Structure du projet Flutter
          lib/ │ ├── main.dart ├── app.dart │ ├── providers/ │ └── cart_provider.dart │ ├── pages/ │ ├── product_page.dart │ └── cart_page.dart
      • Les Fichiers dart
          Fichier main.dart
            import 'package:flutter/material.dart';
            import 'package:provider/provider.dart';
            
            import 'app.dart';
            import 'providers/cart_provider.dart';
            
            void main() {
              runApp(
                ChangeNotifierProvider(
                  create: (_) => CartProvider(),
                  child: MyApp(),
                ),
              );
            }
          Fichier app.dart
            import 'package:flutter/material.dart';
            import 'pages/product_page.dart';
            
            class MyApp extends StatelessWidget {
              @override
              Widget build(BuildContext context) {
                return MaterialApp(
                  title: 'Panier avec Provider',
                  debugShowCheckedModeBanner: false,
                  theme: ThemeData(primarySwatch: Colors.blue),
                  home: ProductPage(),
                );
              }
            }
            
          Fichier providers/cart_provider.dart
            import 'package:flutter/material.dart';
            
            class CartProvider with ChangeNotifier {
              final List<String> _items = [];
            
              List<String> get items => _items;
            
              int get totalItems => _items.length;
            
              void addItem(String item) {
                _items.add(item);
                notifyListeners();
              }
            
              void removeItem(String item) {
                _items.remove(item);
                notifyListeners();
              }
            
              void clearCart() {
                _items.clear();
                notifyListeners();
              }
            }
            
          Fichier pages/product_page.dart
            import 'package:flutter/material.dart';
            import 'package:provider/provider.dart';
            
            import '../providers/cart_provider.dart';
            import 'cart_page.dart';
            
            class ProductPage extends StatelessWidget {
              final List<String> products = [
                "Laptop",
                "Smartphone",
                "Casque",
                "Clavier",
              ];
            
              @override
              Widget build(BuildContext context) {
                final cart = context.read<CartProvider>();
            
                return Scaffold(
                  appBar: AppBar(
                    title: Text("Produits"),
                    actions: [
                      Consumer<CartProvider>(
                        builder: (context, cart, child) {
                          return IconButton(
                            icon: Stack(
                              children: [
                                Icon(Icons.shopping_cart),
                                if (cart.totalItems > 0)
                                  Positioned(
                                    right: 0,
                                    child: CircleAvatar(
                                      radius: 8,
                                      backgroundColor: Colors.red,
                                      child: Text(
                                        cart.totalItems.toString(),
                                        style: TextStyle(
                                          fontSize: 10,
                                          color: Colors.white,
                                        ),
                                      ),
                                    ),
                                  ),
                              ],
                            ),
                            onPressed: () {
                              Navigator.push(
                                context,
                                MaterialPageRoute(builder: (_) => CartPage()),
                              );
                            },
                          );
                        },
                      ),
                    ],
                  ),
                  body: ListView.builder(
                    itemCount: products.length,
                    itemBuilder: (_, index) {
                      return ListTile(
                        title: Text(products[index]),
                        trailing: IconButton(
                          icon: Icon(Icons.add),
                          onPressed: () {
                            cart.addItem(products[index]);
                          },
                        ),
                      );
                    },
                  ),
                );
              }
            }
            
          Fichier pages/cart_page.dart
            import 'package:flutter/material.dart';
            import 'package:provider/provider.dart';
            
            import '../providers/cart_provider.dart';
            
            class CartPage extends StatelessWidget {
              @override
              Widget build(BuildContext context) {
                final cart = context.watch<CartProvider>();
            
                return Scaffold(
                  appBar: AppBar(
                    title: Text("Mon Panier"),
                    actions: [
                      IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () {
                          cart.clearCart();
                        },
                      ),
                    ],
                  ),
                  body: cart.items.isEmpty
                      ? Center(child: Text("Panier vide"))
                      : ListView.builder(
                          itemCount: cart.items.length,
                          itemBuilder: (_, index) {
                            final item = cart.items[index];
                            return ListTile(
                              title: Text(item),
                              trailing: IconButton(
                                icon: Icon(Icons.remove_circle),
                                onPressed: () {
                                  cart.removeItem(item);
                                },
                              ),
                            );
                          },
                        ),
                );
              }
            }
            
      • Créer le dossier des images
          /assets /images laptop.png smartphone.png casque.png clavier.png
        • Utilise des noms sans espace et en minuscules.
        • Déclarer les images dans pubspec.yaml
            
            flutter:
              assets:
                - assets/images/laptop.png
                - assets/images/smartphone.png
                - assets/images/casque.png
                - assets/images/clavier.png
        • Très important :
          • Respecte l'indentation (2 espaces)
          • Redémarre l’application (flutter clean puis flutter run si nécessaire)
        • Créer un modèle Product
        • models/product.dart
            
            class Product {
              final String name;
              final String image;
            
              Product({required this.name, required this.image});
            }
        • Les Fichiers dart
            Fichier main.dart
              import 'package:flutter/material.dart';
              import 'package:provider/provider.dart';
              
              import 'app.dart';
              import 'providers/cart_provider.dart';
              
              void main() {
                runApp(
                  MultiProvider(
                    providers: [
                      ChangeNotifierProvider(create: (_) => CartProvider()),
                    ],
                    child: const MyApp(),
                  ),
                );
              }
            Fichier app.dart
              import 'package:flutter/material.dart';
              import 'pages/product_page.dart';
              
              class MyApp extends StatelessWidget {
                const MyApp({super.key});
              
                @override
                Widget build(BuildContext context) {
                  return MaterialApp(
                    debugShowCheckedModeBanner: false,
                    title: "Exemple Provider",
                    theme: ThemeData(
                      primarySwatch: Colors.blue,
                    ),
                    home: ProductPage(),
                  );
                }
              }
            Fichier providers/cart_provider.dart
              import 'package:flutter/material.dart';
              import '../models/product.dart';
              
              class CartProvider extends ChangeNotifier {
                final List<Product> _items = [];
              
                List<Product> get items => _items;
              
                int get totalItems => _items.length;
              
                void addItem(Product product) {
                  _items.add(product);
                  notifyListeners();
                }
              
                void removeItem(Product product) {
                  _items.remove(product);
                  notifyListeners();
                }
              
                void clearCart() {
                  _items.clear();
                  notifyListeners();
                }
              }
            Fichier pages/product_page.dart
              import 'package:flutter/material.dart';
              import 'package:provider/provider.dart';
              import '../models/product.dart';
              import '../providers/cart_provider.dart';
              import 'cart_page.dart';
              
              class ProductPage extends StatelessWidget {
                ProductPage({super.key});
              
                final List<Product> products = [
                  Product(name: "Laptop", image: "assets/images/laptop.png"),
                  Product(name: "Smartphone", image: "assets/images/smartphone.png"),
                  Product(name: "Casque", image: "assets/images/casque.png"),
                  Product(name: "Clavier", image: "assets/images/clavier.png"),
                ];
              
                @override
                Widget build(BuildContext context) {
                  final cart = context.read<CartProvider>();
              
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text("Nos Produits"),
                      centerTitle: true,
                    ),
                    body: ListView.builder(
                      padding: const EdgeInsets.only(bottom: 100),
                      itemCount: products.length,
                      itemBuilder: (context, index) {
                        final product = products[index];
              
                        return Card(
                          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                          elevation: 3,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: ListTile(
                            leading: CircleAvatar(
                              radius: 26,
                              backgroundImage: AssetImage(product.image),
                            ),
                            title: Text(product.name, style: const TextStyle(fontWeight: FontWeight.bold)),
                            subtitle: const Text("Produit disponible"),
                            trailing: IconButton(
                              icon: const Icon(Icons.add_circle, color: Colors.green, size: 28),
                              onPressed: () => cart.addItem(product),
                            ),
                          ),
                        );
                      },
                    ),
                    bottomNavigationBar: Consumer<CartProvider>(
                      builder: (context, cart, child) {
                        return Container(
                          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
                          decoration: const BoxDecoration(
                            color: Colors.white,
                            boxShadow: [
                              BoxShadow(
                                blurRadius: 10,
                                color: Colors.black12,
                                offset: Offset(0, -2),
                              )
                            ],
                          ),
                          child: ElevatedButton(
                            onPressed: cart.totalItems == 0
                                ? null
                                : () {
                                    Navigator.push(context, MaterialPageRoute(builder: (_) => const CartPage()));
                                  },
                            style: ElevatedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 16),
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(14),
                              ),
                            ),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                const Icon(Icons.shopping_cart, size: 26),
                                const SizedBox(width: 10),
                                Text("Voir le panier (${cart.totalItems})", style: const TextStyle(fontSize: 18)),
                              ],
                            ),
                          ),
                        );
                      },
                    ),
                  );
                }
              }
            Fichier pages/cart_page.dart
              import 'package:flutter/material.dart';
              import 'package:provider/provider.dart';
              import '../providers/cart_provider.dart';
              
              class CartPage extends StatelessWidget {
                const CartPage({super.key});
              
                @override
                Widget build(BuildContext context) {
                  final cart = context.watch<CartProvider>();
              
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text("Mon Panier"),
                      centerTitle: true,
                      actions: [
                        if (cart.items.isNotEmpty)
                          IconButton(
                            tooltip: "Vider le panier",
                            icon: const Icon(Icons.delete_outline),
                            onPressed: cart.clearCart,
                          ),
                      ],
                    ),
                    body: cart.items.isEmpty
                        ? _buildEmptyCart()
                        : Column(
                            children: [
                              Expanded(
                                child: ListView.builder(
                                  padding: const EdgeInsets.all(16),
                                  itemCount: cart.items.length,
                                  itemBuilder: (_, index) {
                                    final product = cart.items[index];
                                    return Card(
                                      elevation: 3,
                                      margin: const EdgeInsets.only(bottom: 12),
                                      shape: RoundedRectangleBorder(
                                        borderRadius: BorderRadius.circular(12),
                                      ),
                                      child: ListTile(
                                        leading: CircleAvatar(
                                          backgroundImage: AssetImage(product.image),
                                          radius: 26,
                                        ),
                                        title: Text(
                                          product.name,
                                          style: const TextStyle(fontWeight: FontWeight.bold),
                                        ),
                                        trailing: IconButton(
                                          icon: const Icon(
                                            Icons.remove_circle,
                                            color: Colors.red,
                                            size: 28,
                                          ),
                                          onPressed: () => cart.removeItem(product),
                                        ),
                                      ),
                                    );
                                  },
                                ),
                              ),
                              Container(
                                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
                                decoration: BoxDecoration(
                                  color: Colors.white,
                                  boxShadow: const [
                                    BoxShadow(
                                      blurRadius: 10,
                                      color: Colors.black12,
                                      offset: Offset(0, -2),
                                    ),
                                  ],
                                ),
                                child: Row(
                                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                  children: [
                                    const Text("Total articles", style: TextStyle(fontSize: 16)),
                                    Text("${cart.totalItems}", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue)),
                                  ],
                                ),
                              ),
                            ],
                          ),
                  );
                }
              
                Widget _buildEmptyCart() {
                  return const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.shopping_cart_outlined, size: 80, color: Colors.grey),
                        SizedBox(height: 16),
                        Text("Votre panier est vide", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        SizedBox(height: 8),
                        Text("Ajoutez des produits pour commencer", style: TextStyle(color: Colors.grey)),
                      ],
                    ),
                  );
                }
              }
            Fichier models/product.dart
              class Product {
                final String name;
                final String image;
              
                Product({required this.name, required this.image});
              }
              
        • Créer le dossier des images
            /assets /images laptop.png smartphone.png casque.png clavier.png
          • Utilise des noms sans espace et en minuscules.
          • Déclarer les images dans pubspec.yaml
              
              flutter:
                assets:
                  - assets/images/
          • Très important :
            • Respecte l'indentation (2 espaces)
            • Redémarre l’application (flutter clean puis flutter run si nécessaire)
          • Créer un modèle Product
          • models/product.dart
              
              class Product {
                final String name;
                final String image;
              
                Product({required this.name, required this.image});
              }