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() 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.
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.
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.
Exemple de base de setState() avec un incrément de compteur. Montre l'utilisation correcte : modification d'un champ dans le callback :
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 :
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.
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 :
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.
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 :
// 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.
Avant d'appeler setState() après une opération asynchrone, vérifiez toujours mounted :
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.
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%.
| Scénario | Alternative | Avantage |
|---|---|---|
| Animation | AnimatedBuilder | Reconstruit seulement le widget animé |
| Flux de données | StreamBuilder | Réagit à chaque élément du flux |
| Résultat futur | FutureBuilder | Gère les états de chargement/erreur |
| Valeur locale | ValueListenableBuilder | Réagit aux changements d'une seule valeur |
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.
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.
mounted dans les callbacks asynchronesFoire aux questions
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.
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.
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.
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.
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é
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.
Lisez aussi