2.1Architecture interne
Avant d'entrer dans les détails du fonctionnement des widgets, il est important de comprendre comment Flutter est construit en interne. Les images ci-dessous présentent l'architecture globale du framework : elles montrent comment les différentes couches — du code Dart que nous écrivons jusqu'au moteur graphique et au système d'exploitation — interagissent pour afficher l'interface à l'écran.
Elles servent de repère visuel pour comprendre le parcours complet d'un widget : depuis sa définition dans le code, jusqu'à son rendu final sous forme de pixels.
2.1.1 – Widgets / Elements / RenderObjects
L'architecture de Flutter repose sur trois couches fondamentales qui travaillent ensemble pour créer et afficher l'interface utilisateur. Comprendre ces trois concepts est essentiel pour maîtriser Flutter et développer des applications efficaces.
🏗️ Vue d'ensemble de l'architecture
Flutter utilise une architecture en trois couches pour gérer l'interface utilisateur :
- Widget : La description immuable de l'interface (le "quoi" et le "comment")
- Element : L'instance mutable qui maintient l'état et la référence au RenderObject (le "qui")
- RenderObject : L'objet responsable du rendu visuel à l'écran (le "dessin")
📦 Les Widgets : la description immuable
Dans Flutter, tout est un Widget. C'est une phrase que vous entendrez souvent, et elle est vraie. Un Widget est une description immuable (immutable) d'une partie de l'interface utilisateur. Il ne contient pas l'état mutable de l'application, mais décrit comment une partie de l'UI devrait être configurée.
Caractéristiques des Widgets
Les Widgets possèdent plusieurs caractéristiques importantes :
- Ils sont immuables : Une fois créé, un Widget ne peut pas être modifié. Pour changer l'interface, vous créez un nouveau Widget.
- Ils sont légers : Les Widgets sont des objets simples qui ne contiennent que des configurations. Ils peuvent être créés et détruits rapidement.
- Ils sont composables : Vous pouvez combiner plusieurs Widgets pour créer des interfaces complexes.
- Ils sont déclaratifs : Vous décrivez ce que vous voulez, pas comment le faire.
Text(
'Bonjour Flutter',
style: TextStyle(
fontSize: 24,
color: Colors.blue,
),
)
Ce Widget Text décrit comment un texte devrait être affiché : son contenu, sa taille, sa couleur. Mais il ne contient pas l'état de l'application ni ne dessine directement à l'écran.
Types de Widgets
Flutter distingue deux grandes catégories de Widgets :
1. Les Widgets stateless (sans état)
Les Widgets StatelessWidget sont des Widgets qui ne changent jamais. Une fois créés, ils restent identiques pendant toute leur durée de vie. Ils sont parfaits pour afficher des informations statiques.
class MonBouton extends StatelessWidget {
final String texte;
const MonBouton({required this.texte});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {},
child: Text(texte),
);
}
}
Ce Widget affiche toujours le même bouton avec le même texte. Il ne change jamais.
2. Les Widgets stateful (avec état)
Les Widgets StatefulWidget peuvent changer au cours du temps. Ils maintiennent un état qui peut être modifié, ce qui déclenche une reconstruction de l'interface. Ils sont utilisés pour les parties de l'UI qui doivent réagir aux interactions utilisateur ou aux changements de données.
class Compteur extends StatefulWidget {
@override
_CompteurState createState() => _CompteurState();
}
class _CompteurState extends State<Compteur> {
int _nombre = 0;
void _incrementer() {
setState(() {
_nombre++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Nombre: $_nombre'),
ElevatedButton(
onPressed: _incrementer,
child: Text('Incrémenter'),
),
],
);
}
}
Ce Widget maintient un état (_nombre) qui peut changer. Quand l'utilisateur clique sur le bouton, setState() est appelé, ce qui reconstruit le Widget avec la nouvelle valeur.
StatefulWidget peut changer, le Widget lui-même reste immuable. C'est l'objet State associé qui contient les données mutables. Quand l'état change, Flutter crée un nouveau Widget avec les nouvelles valeurs.
🔗 Les Elements : le pont entre Widget et RenderObject
Les Elements sont des objets moins connus mais essentiels dans l'architecture Flutter. Ils servent de pont entre les Widgets (la description) et les RenderObjects (le rendu). Un Element est créé pour chaque Widget dans l'arbre et maintient l'état mutable nécessaire à la gestion de l'interface.
Rôle des Elements
Les Elements ont plusieurs responsabilités cruciales :
- Ils maintiennent la référence au RenderObject : L'Element garde le lien avec l'objet qui dessine réellement à l'écran.
- Ils gèrent le cycle de vie : Les Elements sont créés, mis à jour et détruits selon les besoins.
- Ils optimisent les reconstructions : Quand un Widget change, l'Element détermine si le RenderObject doit être mis à jour ou recréé.
- Ils maintiennent l'arbre de l'interface : Les Elements forment un arbre qui reflète la structure des Widgets.
Le processus de mise à jour
Quand vous appelez setState() dans un StatefulWidget, voici ce qui se passe :
- Un nouveau Widget est créé avec les nouvelles valeurs
- Flutter compare le nouveau Widget avec l'ancien (via l'Element)
- Si le Widget est du même type et a la même clé, l'Element met à jour le RenderObject existant
- Si le Widget est différent, l'Element est détruit et recréé
- Le RenderObject est mis à jour ou recréé selon les besoins
Text qui affiche "Bonjour". Si vous changez le texte pour "Au revoir", Flutter :
- Crée un nouveau Widget
Text('Au revoir') - L'Element compare : même type de Widget, même clé
- L'Element met à jour le RenderObject existant avec le nouveau texte
- Le RenderObject redessine uniquement la partie qui a changé
🎨 Les RenderObjects : le rendu à l'écran
Les RenderObjects sont les objets responsables du rendu visuel réel à l'écran. Ils effectuent les calculs de layout (positionnement et taille) et dessinent les pixels. Contrairement aux Widgets qui sont légers et nombreux, les RenderObjects sont plus lourds et sont créés uniquement pour les Widgets qui ont besoin de rendre quelque chose.
Responsabilités des RenderObjects
Les RenderObjects s'occupent de trois tâches principales :
- Le layout (mise en page) : Calculer la position et la taille de chaque élément
- La peinture (painting) : Dessiner les pixels à l'écran
- La composition (compositing) : Organiser les différentes couches visuelles
Le processus de rendu
Quand Flutter doit afficher l'interface, voici le processus complet :
- Phase de layout : Flutter parcourt l'arbre des RenderObjects de haut en bas (parent vers enfant) pour déterminer les contraintes, puis de bas en haut (enfant vers parent) pour calculer les tailles et positions.
- Phase de peinture : Flutter parcourt l'arbre des RenderObjects et chaque RenderObject dessine son contenu dans un canvas.
- Phase de composition : Flutter combine toutes les couches dessinées en une image finale qui est affichée à l'écran.
Types de RenderObjects
Il existe deux grandes catégories de RenderObjects :
1. RenderBox
La plupart des RenderObjects héritent de RenderBox. Ils gèrent des éléments rectangulaires avec une largeur et une hauteur. Les Widgets comme Container, Text, Image utilisent des RenderBox.
Container avec une largeur et une hauteur, Flutter crée un RenderBox qui calcule exactement où placer ce rectangle et comment le dessiner.
2. RenderSliver
Les RenderSliver sont utilisés pour les listes scrollables comme ListView ou GridView. Ils gèrent des éléments qui peuvent être partiellement visibles et qui doivent être rendus de manière optimisée (lazy rendering).
🔄 Comment les trois couches travaillent ensemble
Maintenant que nous avons vu chaque couche individuellement, comprenons comment elles interagissent pour créer l'interface utilisateur.
Le cycle de vie complet
Voici ce qui se passe quand vous créez une interface Flutter :
- Création des Widgets : Vous écrivez du code Dart qui crée un arbre de Widgets (par exemple dans la méthode
build()). - Création des Elements : Flutter parcourt l'arbre de Widgets et crée un Element pour chaque Widget. Les Elements forment un arbre parallèle.
- Création des RenderObjects : Pour les Widgets qui ont besoin de rendre quelque chose (comme
Text,Container), Flutter crée un RenderObject via l'Element. - Layout et peinture : Flutter exécute les phases de layout et de peinture sur l'arbre des RenderObjects pour afficher l'interface.
Column(
children: [
Text('Titre'),
Container(
width: 100,
height: 100,
color: Colors.blue,
),
],
)
- Vous créez 3 Widgets :
Column,Text,Container - Flutter crée 3 Elements correspondants
- Flutter crée 3 RenderObjects (un pour chaque Widget qui doit être rendu)
- Flutter calcule les positions : le Text en haut, le Container en dessous
- Flutter dessine le texte et le rectangle bleu à l'écran
Quand l'interface change
Quand vous modifiez l'interface (par exemple via setState()), Flutter optimise intelligemment les mises à jour :
- Si le Widget est identique : L'Element et le RenderObject sont réutilisés, rien n'est recréé.
- Si le Widget change mais reste du même type : L'Element met à jour le RenderObject avec les nouvelles propriétés.
- Si le Widget change de type : L'Element et le RenderObject sont détruits et recréés.
💡 Pourquoi cette architecture ?
Cette séparation en trois couches peut sembler complexe, mais elle apporte de nombreux avantages :
1. Performance
En séparant la description (Widget) du rendu (RenderObject), Flutter peut créer et détruire des Widgets légers sans impact sur les RenderObjects coûteux. Les Widgets peuvent être recréés à chaque frame si nécessaire, tandis que les RenderObjects sont réutilisés autant que possible.
2. Flexibilité
Les développeurs travaillent uniquement avec des Widgets, qui sont simples et déclaratifs. Flutter gère automatiquement la complexité des Elements et RenderObjects en arrière-plan.
3. Optimisation
L'Element peut comparer les Widgets et décider intelligemment si une mise à jour est nécessaire. Cela évite des recalculs et des redessins inutiles.
4. Testabilité
Les Widgets étant immuables et légers, ils sont faciles à tester. Vous pouvez créer des Widgets dans vos tests sans avoir besoin de créer tout l'environnement de rendu.
📚 Résumé
Pour conclure cette section, retenons les points essentiels :
- Widget : Description immuable de l'interface. Léger, créé et détruit fréquemment.
- Element : Instance mutable qui maintient l'état et le lien avec le RenderObject. Gère le cycle de vie et optimise les mises à jour.
- RenderObject : Objet responsable du rendu visuel. Coûteux, réutilisé autant que possible.
- Les trois couches travaillent ensemble pour créer une interface performante et réactive.
- Cette architecture permet à Flutter d'optimiser les performances en réutilisant les RenderObjects et en ne mettant à jour que ce qui est nécessaire.