setState() — essence, mécanisme de fonctionnement et application

Auteur : IT Sectr Publié le : 2026-07-01 Temps de lecture : 9 min

setState() est la méthode clé de State dans Flutter, notifyant le framework des changements de données et déclenchant la reconstruction de l'interface. Selon la documentation officielle de Flutter (Flutter.dev, 2026), setState est le principal mécanisme de réactivité dans StatefulWidget : sans son appel, l'UI ne connaît pas les changements des champs de State et reste dans son état précédent. La méthode accepte un VoidCallback, à l'intérieur duquel le développeur modifie les champs modifiables, après quoi Flutter appelle automatiquement build pour reconstruire le widget.

Points clés

  • setState() — une méthode de State qui marque le widget comme sale (dirty) et planifie la reconstruction de l'UI dans l'image suivante
  • Callback — setState accepte un VoidCallback, à l'intérieur duquel toutes les modifications des champs de State affectant l'interface doivent être effectuées
  • Asynchronie — setTimeout ou Future dans setState ne garantissent pas la synchronie ; les mutations après await doivent être dans un autre setState
  • Performance — chaque appel à setState reconstruit tout le widget ; pour minimiser, utilisez des widgets enfants const
  • mounted — avant d'appeler setState dans les callbacks asynchrones, vérifiez toujours mounted, sinon — exception

Qu'est-ce que setState() ?

setState() est une méthode intégrée de la classe State dans Flutter, conçue pour notifier le framework que l'état interne du widget a changé et que l'UI doit être reconstruite. Sans appeler setState, Flutter ne sait pas qu'il y a eu des changements — même si les champs de State ont été modifiés, l'interface reste inchangée jusqu'à la prochaine reconstruction forcée par le parent.

Signature de la méthode : void setState(VoidCallback fn). Le callback est exécuté de manière synchrone dans setState, et ce n'est qu'après son achèvement que State est marqué comme sale. Cela garantit que toutes les modifications sont appliquées atomiquement avant la reconstruction. Selon la spécification du langage Dart (Dart Team, 2026), l'atomicité de setState empêche les conditions de course où build pourrait voir un état partiellement mis à jour.

setState ne prend pas d'arguments, ne retourne pas de valeur et ne peut pas être redéfini. C'est une méthode finale (scellée) de la classe State. Le développeur ne peut pas changer son comportement — seulement l'utiliser comme prévu. Tenter d'appeler setState en dehors de State (par exemple, depuis une autre classe) est impossible car la méthode est déclarée dans la classe State.

setState ne change pas l'état — c'est vous qui le faites

Une idée fausse courante est de penser que setState lui-même change l'état. Ce n'est pas vrai. setState appelle seulement le callback passé (dans lequel le développeur modifie les champs) puis signale au framework le besoin de build. Le callback est obligatoire — passer null ou un callback vide provoquera une erreur.

Comment fonctionne setState() ?

Le mécanisme de fonctionnement de setState() peut être divisé en quatre étapes. Première — appel de la méthode avec un callback. Deuxième — exécution synchrone du callback, à l'intérieur duquel les champs de State sont modifiés. Troisième — State est marqué comme sale dans un champ spécial _dirty. Quatrième — à la fin de la micro-tâche actuelle, Flutter parcourt tous les éléments sales et appelle leur build dans l'ordre d'apparition dans l'arbre.

Un détail important : setState n'appelle pas build immédiatement. Flutter utilise une stratégie de mise à jour par lots : tous les éléments sales sont collectés et reconstruits en une seule image. Cela signifie que si setState est appelé plusieurs fois dans un même bloc synchrone, build ne s'exécutera qu'une seule fois — après que toutes les modifications soient terminées. Cette optimisation évite de multiples reconstructions par image.

Selon l'équipe du moteur Flutter (Google, 2025), le mécanisme du drapeau sale est basé sur le passage BuildOwner._dirtyElements. Chaque StatefulElement sale est ajouté à la liste et traité à l'étape de mise à jour de l'image. Si un widget a été retiré de l'arbre avant le traitement, il est automatiquement exclu de la liste des éléments sales.

Garanties de setState

  • Le callback est exécuté de manière synchrone avant le marquage sale
  • build est appelé pas plus d'une fois par image (même avec plusieurs appels à setState)
  • La mise à jour de l'UI se produit sur l'image suivante (généralement ~16ms à 60 FPS)
  • Après dispose, appeler setState est interdit — lève une exception
  • Pendant build, appeler setState est interdit — boucle infinie

Exemples de code Dart

Exemple de base de setState() avec un incrément de compteur. Montre l'utilisation correcte : modification d'un champ dans le callback :

dart
class _CounterState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++; // modification du champ dans le callback
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _increment,
      child: Text('$_count'),
    );
  }
}

Exemple avec un champ de texte et un contrôleur — setState() pour la gestion de la visibilité du mot de passe :

dart
class _PasswordFieldState extends State<PasswordField> {
  bool _obscured = true;
  final _controller = TextEditingController();

  void _toggleVisibility() {
    setState(() {
      _obscured = !_obscured;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      obscureText: _obscured,
      decoration: InputDecoration(
        suffixIcon: IconButton(
          icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
          onPressed: _toggleVisibility,
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Dans cet exemple, setState() change seulement le champ booléen _obscured, ce qui déclenche la reconstruction du TextField avec une nouvelle icône et un mode d'affichage. Le contrôleur de texte n'est pas recréé — il est initialisé une fois dans initState et libéré dans dispose.

Mutations multiples dans un seul setState

Si vous devez modifier plusieurs champs, toutes les modifications doivent être effectuées dans un seul setState. Cela garantit que build verra un état cohérent :

dart
setState(() {
  _isLoading = false;
  _items = newItems;
  _error = null;
});

Trois champs sont modifiés dans un seul callback — build s'exécutera une fois et verra toutes les modifications simultanément. Si chaque appel était un setState séparé, build s'exécuterait quand même une seule fois grâce au traitement par lots des éléments sales.

Asynchronie et setState

L'une des nuances les plus importantes de setState() est son comportement avec les opérations asynchrones. Le callback de setState s'exécute de manière synchrone, mais si await est appelé à l'intérieur, le code après await s'exécutera après que setState ait déjà terminé son travail. Cela signifie que les modifications de champ après await ne seront pas capturées par le setState actuel.

L'approche correcte : l'opération asynchrone est effectuée en dehors de setState, et setState est appelé après son achèvement. Tout le code entre la réception du résultat et l'appel à setState s'exécute dans un contexte synchrone après await :

dart
// CORRECT : await en dehors de setState
Future<void> _loadData() async {
  final result = await ApiService.fetchData();
  setState(() {
    _data = result;
    _isLoading = false;
  });
}

// FAUX : await dans setState — aucune garantie de mise à jour
void _loadDataWrong() {
  setState(() async {
    _data = await ApiService.fetchData(); // setState retourne avant la fin de await
    _isLoading = false; // ce code n'est pas capturé par setState
  });
}

Selon la documentation Flutter (Dart async patterns, 2026), passer un callback asynchrone à setState est un anti-patron car setState attend un VoidCallback (fonction synchrone), alors qu'une fonction asynchrone retourne un Future qui est ignoré. Les modifications après le premier await dans un tel callback ne seront pas correctement traitées par le framework.

Vérification de mounted dans les scénarios asynchrones

Avant d'appeler setState() après une opération asynchrone, vérifiez toujours mounted :

dart
if (mounted) {
  setState(() => _data = data);
}

Si le widget a été retiré de l'arbre pendant l'opération asynchrone, mounted deviendra false et setState ne sera pas appelé. Cela évite les exceptions et les fuites de ressources.

Performance et optimisation

setState() est un mécanisme pratique mais potentiellement coûteux s'il est utilisé sans réflexion. Chaque appel à setState reconstruit tout le widget et tous ses descendants (s'ils ne sont pas const). Dans les arbres profonds ou avec des appels fréquents, cela peut entraîner des baisses de FPS.

Principales stratégies d'optimisation : minimiser la zone de reconstruction (extraire les parties modifiables de l'UI dans des StatefulWidgets séparés), utiliser const pour les enfants immuables et éviter d'appeler setState dans les widgets parents si seulement un petit détail UX a changé. Si l'état est mis à jour à haute fréquence (animation, flux de données), envisagez AnimatedBuilder ou ValueListenableBuilder.

Selon les meilleures pratiques de performance Flutter (Flutter.dev, février 2026), le profilage d'applications réelles montre que jusqu'à 40% de tous les appels à setState peuvent être remplacés par des widgets enfants const ou des constructeurs réactifs (StreamBuilder, FutureBuilder). Cela réduit le temps moyen de construction d'image de 15 à 25%.

Quand setState est redondant

ScénarioAlternativeAvantage
AnimationAnimatedBuilderReconstruit seulement le widget animé
Flux de donnéesStreamBuilderRéagit à chaque élément du flux
Résultat futurFutureBuilderGère les états de chargement/erreur
Valeur localeValueListenableBuilderRéagit aux changements d'une seule valeur

Alternatives à setState

Malgré la polyvalence de setState(), dans les grands projets, il est principalement utilisé pour l'état local. Pour l'état global ou partagé, des solutions spécialisées sont utilisées, chacune remplaçant ou encapsulant setState.

Provider utilise ChangeNotifier + notifyListeners comme analogue de setState, mais avec la possibilité d'abonner plusieurs widgets. Bloc utilise des Streams — l'état est modifié en ajoutant des événements à un StreamController. Riverpod combine les approches, fournissant une gestion locale (StateProvider) et asynchrone (AsyncNotifier) sans liaison à StatefulWidget. Les trois approches éliminent le besoin d'appeler manuellement setState — les mises à jour de l'UI se produisent automatiquement lorsque les données changent.

Selon l'enquête de la communauté Flutter 2025 (Flutter Foundation, décembre 2025), 74% des développeurs utilisent au moins un outil de gestion d'état en plus de setState. Dans le même temps, 92% continuent d'utiliser setState pour les données locales des champs de texte, cases à cocher ou compteurs simples — ceci est considéré comme une bonne pratique.

Quand conserver setState

  • L'état est utilisé par un seul widget
  • Valeur booléenne ou numérique simple (focus, visibilité, compteur)
  • Prototypage et expériences rapides
  • Les contrôleurs (TextEditingController, PageController) nécessitent toujours StatefulWidget

Erreurs courantes

La première et plus dangereuse erreur est d'appeler setState après dispose. Une opération asynchrone démarrée dans initState, l'utilisateur a quitté l'écran, le widget est supprimé et le callback asynchrone appelle setState — l'application plante avec une exception. Solution — vérifiez toujours mounted avant d'appeler.

La deuxième erreur est d'appeler setState dans build. Cela conduit à une boucle infinie : build → setState → sale → build → setState → ... Flutter ne bloque pas un tel appel (vous obtiendrez une StackOverflowError). setState ne peut être appelé qu'en réponse à un événement (pression d'un bouton, achèvement d'un Future, données d'un flux).

La troisième erreur est de modifier les champs de State sans appeler setState. Le développeur écrit _count++ et s'attend à ce que l'UI se mette à jour. Flutter ne peut pas suivre automatiquement les modifications de champ — il a besoin d'un signal explicite via setState. C'est une différence fondamentale avec les frameworks réactifs comme Vue.js, où les changements de données déclenchent automatiquement les mises à jour.

La quatrième erreur est d'appeler setState avec un callback asynchrone (lambda async). Comme décrit dans la section sur l'asynchronie, les modifications après await ne seront pas capturées, ce qui entraîne des bugs difficiles à reproduire. Utilisez un callback synchrone et appelez setState après await.

Liste de vérification pour un setState sûr

  • Toujours vérifier mounted dans les callbacks asynchrones
  • Ne pas appeler setState dans build
  • Ne pas passer de lambda async à setState
  • Ne pas modifier les champs de State en dehors de setState
  • Si vous modifiez plusieurs champs — faites-le dans un seul setState

Foire aux questions

Que fait setState() dans Flutter ?

setState() notifie Flutter que les données internes de StatefulWidget ont changé et que l'UI doit être reconstruite. La méthode accepte un callback, l'exécute de manière synchrone, marque le widget comme sale et planifie l'appel de build dans l'image suivante.

Que se passe-t-il si je n'appelle pas setState après avoir modifié un champ ?

L'UI ne se mettra pas à jour. Flutter ne suit pas automatiquement les modifications de champ. La valeur du champ change en mémoire, mais le widget reste dans son état précédent jusqu'à la prochaine reconstruction forcée par le parent.

Puis-je appeler setState à l'intérieur de build ?

Non. Cela conduit à une boucle infinie : build appelle setState, qui marque le widget comme sale et appelle à nouveau build. Flutter ne bloque pas cette situation — l'application plantera avec StackOverflowError.

Combien de fois build s'exécutera-t-il avec deux appels consécutifs à setState ?

Build s'exécutera une fois. Flutter collecte tous les éléments sales et les reconstruit par lots à la fin de l'image. Le deuxième setState avant le traitement ajoute simplement l'élément à la même liste d'éléments sales — aucune reconstruction répétée n'a lieu.

Qu'est-ce que mounted et pourquoi est-ce important pour setState ?

mounted est un drapeau booléen indiquant que le widget est toujours dans l'arbre. Si setState est appelé après une opération asynchrone sans vérifier mounted et que le widget a déjà été supprimé — l'application plante avec l'exception « setState called after dispose ».

Résumé

  • setState() — une méthode de State qui notifie Flutter des changements de données et déclenche la reconstruction de l'UI dans l'image suivante
  • Mécanisme de fonctionnement — exécution synchrone du callback, marquage de State comme sale, reconstruction par lots de tous les éléments sales à la fin de l'image
  • Asynchronie — les callbacks asynchrones dans setState ne fonctionnent pas ; await doit être à l'extérieur, et setState après avoir obtenu le résultat
  • mounted — vérification obligatoire avant setState dans les opérations asynchrones pour éviter les exceptions
  • Optimisation — minimiser la zone de reconstruction avec des widgets enfants const et déplacer les animations vers AnimatedBuilder
  • Alternatives — pour l'état global, utilisez Riverpod, Bloc ou Provider ; conservez setState pour les données locales
  • Règle — n'appelez pas setState dans build, ne passez pas de lambdas async, vérifiez toujours mounted

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi