Gestion d’état avec Provider dans Flutter


Avatar de Pierre Courtois

Gérer vos variables avec setState peut vite devenir un casse-tête lorsque votre application se complexifie. Une solution va être d’utiliser Provider à la place, afin de gérer l’état de vos variables de manière centralisée. Je vous explique tout cela dans ce guide très simple.


Gestion d’état avec Provider dans Flutter

Qu’est-ce que Provider et pourquoi l’utiliser à la place de setState ?

Dans un précédent article, nous avons exploré la gestion d’état avec setState, une méthode intégrée à Flutter qui convient parfaitement pour des applications simples ou des changements d’état locaux. Cependant, lorsque votre application commence à grandir – avec plusieurs écrans, des widgets imbriqués et des données partagées entre différentes parties de l’interface – setState montre vite ses limites. La gestion manuelle des mises à jour devient lourde, le code perd en lisibilité et les performances peuvent en souffrir à cause des reconstructions inutiles.

C’est là qu’intervient Provider, une solution populaire et légère pour la gestion d’état dans Flutter. Recommandé par l’équipe Flutter elle-même, Provider est une bibliothèque qui simplifie le partage et la mise à jour des données à travers une application, tout en restant efficace et facile à comprendre. Contrairement à setState, qui oblige à reconstruire manuellement des widgets, Provider adopte une approche réactive : il notifie automatiquement les widgets concernés lorsqu’un état change, sans toucher aux autres. Pour des applications complexes, cela signifie un code plus propre, une meilleure scalabilité et une maintenance simplifiée.

En quoi Provider est-il plus efficace que setState ?

Prenons un exemple tout simple pour voir en quoi Provider est plus efficace que setState pour conserver les valeurs de vos variables à travers votre application.

Admettons que vous ayez un bouton qui compte combien de fois, vous cliquez dessus et qui utilise setState pour mettre à jour le nombre, après chaque clic. Cette méthode marche bien sur une seule page, mais devient compliquée si vous souhaitez afficher ce nombre sur une autre page. Il va vous falloir trouver un moyen de le transmettre à travers vos widgets, ce qui peut vite être compliqué.

class Compteur extends StatefulWidget {
  @override
  _CompteurState createState() => _CompteurState();
}

class _CompteurState extends State<Compteur> {
  int nombre = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Nombre : $nombre'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              nombre++;
            });
          },
          child: Text('Cliquez-moi'),
        ),
      ],
    );
  }
}

Maintenant, faisons la même chose, mais avec Provider. À la place de garder la valeur de votre nombre dans un widget, vous allez le placer dans une classe spéciale (comme une boîte à outils) pourque tous vos widgets puissent y accéder. Quand le nombre change, Provider prévient alors automatiquement les widgets qui l’utilisent et le met à jour.

// La boîte à outils
class CompteurManager extends ChangeNotifier {
  int nombre = 0;

  void incrementer() {
    nombre++;
    notifyListeners(); // Dit aux widgets : "Hey, j’ai changé !"
  }
}

// Dans votre application
ElevatedButton(
  onPressed: () {
    Provider.of<CompteurManager>(context, listen: false).incrementer();
  },
  child: Text('Cliquez-moi'),
)

// Et ailleurs, pour afficher
Consumer<CompteurManager>(
  builder: (context, compteur, child) {
    return Text('Nombre : ${compteur.nombre}');
  },
)

Comment mettre en place Provider pour votre application Flutter ?

Pour comprendre comment mettre en place Provider, imaginons une application ayant une page avec un champ de texte où l’utilisateur tape un mot, et une liste qui se met à jour en temps réel avec les mots soumis. Avec setState, cela nécessiterait de passer des callbacks ou de gérer manuellement les états à travers les widgets. Avec Provider, tout devient plus fluide. Voici comment procéder, étape par étape.

Étape 1 : Ajouter la dépendance Provider

Tout d’abord, assurez-vous d’ajouter le package provider (6.1.2) à votre projet. Dans votre fichier pubspec.yaml, incluez :

dependencies:
  provider: ^6.1.2

Exécutez ensuite flutter pub get pour installer la dépendance.

Étape 2 : Créer une classe de gestion d’état

Provider repose sur une classe qui contient les données et la logique de votre application. Appelons cette classe WordManager. Elle hérite de ChangeNotifier, ce qui lui permet de notifier les widgets lorsqu’un changement survient.

import 'package:flutter/material.dart';

class WordManager extends ChangeNotifier {
  String _currentWord = '';
  List<String> _wordList = [];

  String get currentWord => _currentWord;
  List<String> get wordList => _wordList;

  void updateWord(String newWord) {
    _currentWord = newWord;
    notifyListeners(); // Notifie les widgets abonnés au changement
  }

  void addWord() {
    if (_currentWord.isNotEmpty) {
      _wordList.add(_currentWord);
      _currentWord = ''; // Réinitialise le champ après ajout
      notifyListeners();
    }
  }
}

Ici, WordManager gère deux variables : _currentWord (le mot en cours de saisie) et _wordList (la liste des mots). Chaque fois que ces données changent, notifyListeners() informe les widgets qui dépendent de cet état.

Étape 3 : Intégrer Provider dans l’arbre des widgets

Pour rendre cet état accessible partout dans l’application, placez un ChangeNotifierProvider au sommet de votre arbre de widgets, généralement dans main.dart.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'word_manager.dart';
import 'home_page.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => WordManager(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo Provider',
      home: HomePage(),
    );
  }
}

Le ChangeNotifierProvider encapsule votre application et fournit une instance de WordManager à tous les widgets descendants.

Étape 4 : Construire l’interface avec Provider

Ne reste plus qu’à créer une page (HomePage) avec un champ de texte et une liste. Les widgets écouteront les changements via Provider.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'word_manager.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Gestion avec Provider')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            // Champ de texte
            TextField(
              onChanged: (value) {
                Provider.of<WordManager>(context, listen: false).updateWord(value);
              },
              decoration: InputDecoration(labelText: 'Entrez un mot'),
            ),
            SizedBox(height: 20),
            // Affichage du mot en temps réel
            Consumer<WordManager>(
              builder: (context, wordManager, child) {
                return Text('Mot actuel : ${wordManager.currentWord}');
              },
            ),
            SizedBox(height: 20),
            // Bouton pour ajouter à la liste
            ElevatedButton(
              onPressed: () {
                Provider.of<WordManager>(context, listen: false).addWord();
              },
              child: Text('Ajouter à la liste'),
            ),
            SizedBox(height: 20),
            // Liste des mots
            Expanded(
              child: Consumer<WordManager>(
                builder: (context, wordManager, child) {
                  return ListView.builder(
                    itemCount: wordManager.wordList.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(wordManager.wordList[index]),
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Explications du code

  • Provider.of : Utilisé pour accéder au WordManager et appeler ses méthodes (comme updateWord ou addWord). L’option listen: false indique qu’on ne veut pas reconstruire le widget à cet endroit, seulement agir sur l’état.
  • Consumer : Ce widget écoute les changements dans WordManager et reconstruit uniquement les parties concernées (par exemple, le texte ou la liste) quand notifyListeners() est appelé.

Modifier une variable avec Provider

Avec Provider, modifier l’état reste aussi simple que de changer des variables dans une classe, comme vous le feriez avec setState. La différence principale est que tout est centralisé dans votre ChangeNotifier. Voici quelques opérations de base :

Modifier une valeur simple

Voici comment modifier un compte, avec provider :

class CounterManager extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;           // Change la valeur
    notifyListeners(); // Préviens les widgets
  }
}

On peut nesuite appeler la fonction avec :

Provider.of<CounterManager>(context, listen: false).increment();

Ajouter un élément à une liste

class ListManager extends ChangeNotifier {
  List<String> items = [];

  void addItem(String item) {
    items.add(item);   // Ajoute à la liste
    notifyListeners(); // Met à jour l’écran
  }
}

Supprimer un élément d’une liste

void removeItem(String item) {
  items.remove(item); // Supprime l’élément
  notifyListeners();  // Préviens les widgets
}

Multiplier une valeur

void multiplyCount(int factor) {
  count *= factor;   // Multiplie
  notifyListeners(); // Met à jour
}

Conclusion

Provider est donc une manière bien plus efficace de gérer toutes les variables dont votre application a besoin de fonctionner et est une méthode mise en avant par l’équipe Flutter. Toutefois, une nouvelle méthode, encore plus efficace tend à émerger : Riverpod.

Avatar de Pierre Courtois