InheritedWidget est un widget spécial dans Flutter qui transmet les données vers le bas dans l'arbre de widgets sans les passer explicitement via les constructeurs. Les widgets enfants accèdent aux données via BuildContext et s'abonnent automatiquement aux mises à jour. Lorsque les données dans InheritedWidget changent, tous les widgets dépendants se reconstruisent. Selon Flutter API Reference, 2025, InheritedWidget est à la base de Theme, MediaQuery, Localizations et de la plupart des bibliothèques de gestion d'état.
Points clés
InheritedWidget est un widget qui rend ses données disponibles pour tous les descendants dans l'arbre Widget Tree. Contrairement à un widget ordinaire qui ne transmet les données qu'via les constructeurs aux éléments enfants, InheritedWidget permet à n'importe quel widget dans le sous-arbre d'accéder aux données sans chaîne de paramètres. Cela résout le problème de « prop drilling » — la transmission de données à travers de nombreux widgets intermédiaires qui n'utilisent pas ces données eux-mêmes.
Flutter inclut plusieurs InheritedWidget intégrés : Theme (schéma de couleurs et styles), MediaQuery (taille d'écran, orientation, densité de pixels), Localizations (chaînes localisées), Directionality (direction du texte), DefaultTextStyle (style de texte par défaut). Ces widgets sont définis par des widgets racines comme MaterialApp et sont disponibles dans toute l'application.
InheritedWidget n'a pas d'état propre — il stocke les données transmises via le constructeur. Lorsque le parent d'InheritedWidget est reconstruit avec de nouvelles données, la méthode updateShouldNotify est appelée pour comparer les anciennes et nouvelles données. Si la méthode retourne true, tous les widgets dépendants sont marqués pour reconstruction. C'est un mécanisme de mise à jour réactive simple mais efficace.
Le mécanisme de transmission de données via InheritedWidget est basé sur l'Element Tree. Lorsqu'un widget appelle dependOnInheritedWidgetOfExactType, l'élément correspondant enregistre une dépendance sur InheritedElement. Lorsque InheritedWidget change, InheritedElement notifie tous les éléments dépendants, qui se reconstruisent dans l'image suivante.
La méthode dependOnInheritedWidgetOfExactType ne trouve pas seulement InheritedWidget dans l'arbre — elle abonne l'élément actuel aux notifications. Si vous utilisiez findAncestorWidgetOfExactType au lieu de dependOn, le widget obtiendrait les données mais ne se reconstruirait pas lorsqu'elles changent. C'est une différence importante : dependOn est un abonnement, findAncestor est une recherche unique.
Lorsqu'un widget demande un InheritedWidget, Flutter remonte l'Element Tree de l'élément actuel jusqu'à la racine, vérifiant chaque InheritedElement pour une correspondance de type. Le premier InheritedElement correspondant est retourné. Cela signifie que l'InheritedWidget le plus proche dans l'arbre a priorité — vous pouvez remplacer les données à un niveau spécifique en plaçant InheritedWidget plus près des descendants.
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
Dans cet exemple, MyTheme utilise une méthode statique of pour fournir des données aux descendants. La méthode dependOnInheritedWidgetOfExactType enregistre une dépendance, et updateShouldNotify compare les anciennes et nouvelles données pour déterminer si les widgets dépendants doivent être reconstruits.
Créer un InheritedWidget personnalisé consiste en deux étapes : définir une classe qui étend InheritedWidget, et implémenter une méthode statique of pour l'accès depuis les descendants. Les données sont transmises via le constructeur, et la méthode updateShouldNotify détermine quand les widgets dépendants doivent se reconstruire.
La classe doit étendre InheritedWidget et accepter les données via un constructeur avec un paramètre child obligatoire. Les données peuvent être de n'importe quel type : primitifs, objets, fonctions. La règle principale est que les données doivent être immuables afin que les anciennes et nouvelles valeurs puissent être comparées de manière fiable.
La méthode statique of prend BuildContext et retourne les données d'InheritedWidget. En interne, elle appelle dependOnInheritedWidgetOfExactType, qui trouve l'InheritedWidget le plus proche du type spécifié dans l'arbre. Si InheritedWidget n'est pas trouvé, la méthode lance une exception ou retourne une valeur par défaut selon l'implémentation.
Pour accéder aux données, le widget appelle MyWidget.of(context) dans la méthode build. Flutter abonne automatiquement le widget aux mises à jour. Si les données changent, le widget se reconstruit dans l'image suivante. Cela permet un code propre et déclaratif sans paramètres inutiles.
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
Dans cet exemple, UserPreferences stocke les préférences utilisateur. La méthode updateShouldNotify compare chaque champ individuellement, ce qui évite les reconstructions inutiles lorsqu'un seul paramètre change. Utilisez une approche similaire pour vos propres InheritedWidget avec plusieurs champs.
updateShouldNotify est la méthode clé d'InheritedWidget qui détermine si les widgets dépendants doivent être notifiés des changements de données. Si la méthode retourne false, les widgets dépendants ne se reconstruisent pas, même si InheritedWidget lui-même a reçu une nouvelle instance avec les mêmes données. C'est d'une importance cruciale pour les performances.
Comparez uniquement les champs qui ont réellement changé et affectent l'affichage. Si InheritedWidget contient 10 champs mais qu'un seul affecte l'interface utilisateur, vérifiez uniquement ce champ. Pour les collections, utilisez une comparaison profonde ou des structures de données immuables. N'utilisez pas == pour List ou Map, car ils comparent par référence.
L'erreur la plus courante est de retourner true sans comparaison. Cela entraîne la reconstruction de tous les widgets dépendants à chaque mise à jour du parent, même si les données n'ont pas changé. La deuxième erreur est de retourner false lorsque les données ont changé, ce qui conduit à une interface utilisateur obsolète. La troisième est une comparaison complexe qui s'exécute à chaque image et ralentit les performances.
InheritedWidget et les callbacks (passer des fonctions via les constructeurs) résolvent des problèmes différents. InheritedWidget est adapté aux données nécessaires à de nombreux widgets à différents niveaux de l'arbre. Les callbacks sont pratiques pour la transmission unidirectionnelle d'événements du parent à un enfant spécifique ou vice versa. Le choix dépend de l'architecture de l'application et de la fréquence des mises à jour.
Utilisez InheritedWidget lorsque les données sont nécessaires à de nombreux widgets à différents niveaux d'imbrication : thème de l'application, préférences utilisateur, informations sur l'appareil, données de session actuelles. InheritedWidget est particulièrement efficace pour les données « globales » qui changent rarement mais sont nécessaires dans différentes parties de l'interface utilisateur.
Les callbacks (fonctions de rappel) sont adaptés pour transmettre des événements d'un widget enfant à un parent : pression de bouton, sélection d'élément de liste, soumission de formulaire. Les callbacks indiquent explicitement quelles actions un enfant peut effectuer et ne créent pas de dépendances cachées. Pour transmettre des données vers le bas dans l'arbre sur un petit nombre de niveaux, il est également plus simple d'utiliser des paramètres de constructeur.
| Critère | InheritedWidget | Callbacks |
|---|---|---|
| Direction | Du haut vers le bas (parent → descendants) | Du bas vers le haut (enfant → parent) ou direct |
| Portée | Sous-arbre entier | Widget spécifique |
| Reconstruction | Automatique lors du changement de données | Nécessite setState manuel |
| Complexité | Moyenne (nécessite une classe InheritedWidget) | Faible (juste une fonction) |
Provider et Riverpod sont des bibliothèques populaires de gestion d'état dans Flutter construites sur InheritedWidget. Elles étendent ses capacités : ajoutent le support de ChangeNotifier, la suppression automatique lors du démontage, l'initialisation paresseuse et une syntaxe simplifiée avec les génériques.
Provider utilise InheritedWidget pour transmettre un objet de n'importe quel type vers le bas dans l'arbre. ChangeNotifierProvider suit les changements via ChangeNotifier et appelle updateShouldNotify lorsque notifyListeners est appelé. Cela libère le développeur de la création manuelle d'InheritedWidget et de l'implémentation d'updateShouldNotify.
InheritedWidget direct donne plus de contrôle et ne nécessite pas de dépendances externes. Provider fournit une infrastructure prête : Consumer, Selector, MultiProvider, ProxyProvider. Le choix dépend de la complexité de l'application. Pour les projets simples, InheritedWidget direct est suffisant ; pour les grands, Provider ou Riverpod réduisent le code passe-partout.
// InheritedWidget direct
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// Équivalent Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Les deux approches dans l'exemple résolvent le même problème — transmettre UserData vers le bas dans l'arbre. Provider réduit le volume de code mais cache la mécanique d'InheritedWidget. InheritedWidget direct donne un contrôle total et une compréhension de ce qui se passe, ce qui est particulièrement important lors de l'apprentissage de Flutter et du débogage de problèmes complexes de reconstruction.
Foire aux questions
InheritedWidget rend les données disponibles pour tous les descendants via BuildContext, tandis qu'un widget ordinaire ne transmet les données que via le constructeur. InheritedWidget abonne également les descendants aux mises à jour des données.
Les widgets dépendants se reconstruisent uniquement lorsque updateShouldNotify retourne true. Si la méthode est correctement implémentée, la reconstruction se produit uniquement lorsque les données changent réellement, pas à chaque reconstruction du parent.
Oui, vous pouvez utiliser n'importe quel nombre d'InheritedWidget dans le même arbre. Chacun fournit des données d'un type spécifique, et les widgets peuvent obtenir des données de plusieurs InheritedWidget simultanément.
dependOn abonne le widget aux mises à jour — lorsque les données changent, le widget se reconstruit. findAncestor effectue une recherche unique sans abonnement, et le widget ne sera pas informé des changements de données.
Pour un état simple (thème, préférences) InheritedWidget est suffisant. Pour un état complexe avec logique métier, utilisez Provider, Riverpod ou BLoC — ils sont construits sur InheritedWidget et ajoutent l'infrastructure nécessaire.
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