CHAPITRE 2.1

Architecture interne

Comprendre les fondements de l'architecture Flutter
Dans cette section, vous allez plonger au cœur de l'architecture interne de Flutter. Vous découvrirez comment Flutter construit et gère l'interface utilisateur à travers trois concepts fondamentaux : les Widgets, les Elements et les RenderObjects. Cette compréhension approfondie vous permettra de développer des applications plus performantes et mieux structurées.

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.

Schéma de l'architecture Flutter
Architecture globale de Flutter : du code Dart (widgets) jusqu'au moteur graphique et à l'affichage

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")
Architecture interne de Flutter : Widgets, Elements, RenderObjects
Schéma des trois couches principales de l'architecture Flutter
Une analogie simple : imaginez que vous construisez une maison. Le Widget est le plan architectural (la description), l'Element est le chef de chantier qui coordonne (gère l'état), et le RenderObject est l'ouvrier qui pose réellement les briques (dessine à l'écran).

📦 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.
Voici un exemple simple de Widget :
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.
Même si un 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.
En tant que développeur, vous n'interagissez généralement pas directement avec les Elements. Flutter les gère automatiquement. Mais comprendre leur existence vous aide à comprendre comment Flutter optimise les performances.

Le processus de mise à jour

Quand vous appelez setState() dans un StatefulWidget, voici ce qui se passe :

  1. Un nouveau Widget est créé avec les nouvelles valeurs
  2. Flutter compare le nouveau Widget avec l'ancien (via l'Element)
  3. Si le Widget est du même type et a la même clé, l'Element met à jour le RenderObject existant
  4. Si le Widget est différent, l'Element est détruit et recréé
  5. Le RenderObject est mis à jour ou recréé selon les besoins
Imaginons que vous avez un Widget Text qui affiche "Bonjour". Si vous changez le texte pour "Au revoir", Flutter :
  1. Crée un nouveau Widget Text('Au revoir')
  2. L'Element compare : même type de Widget, même clé
  3. L'Element met à jour le RenderObject existant avec le nouveau texte
  4. Le RenderObject redessine uniquement la partie qui a changé
C'est très efficace ! Flutter ne recrée pas tout, il met à jour seulement ce qui est nécessaire.

🎨 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 :

  1. 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.
  2. Phase de peinture : Flutter parcourt l'arbre des RenderObjects et chaque RenderObject dessine son contenu dans un canvas.
  3. Phase de composition : Flutter combine toutes les couches dessinées en une image finale qui est affichée à l'écran.
Les RenderObjects sont des objets coûteux à créer. C'est pourquoi Flutter essaie de les réutiliser autant que possible. Quand un Widget change mais que son RenderObject peut être réutilisé, Flutter met simplement à jour les propriétés du RenderObject existant plutôt que d'en créer un nouveau.

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.

Quand vous créez un 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).

Les RenderSliver sont plus complexes car ils doivent gérer le scroll et ne rendre que les éléments visibles. C'est ce qui permet à Flutter d'afficher des listes de milliers d'éléments sans problème de performance.

🔄 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 :

  1. Création des Widgets : Vous écrivez du code Dart qui crée un arbre de Widgets (par exemple dans la méthode build()).
  2. 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.
  3. 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.
  4. Layout et peinture : Flutter exécute les phases de layout et de peinture sur l'arbre des RenderObjects pour afficher l'interface.
Prenons un exemple concret :
Column(
  children: [
    Text('Titre'),
    Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
  ],
)
  1. Vous créez 3 Widgets : Column, Text, Container
  2. Flutter crée 3 Elements correspondants
  3. Flutter crée 3 RenderObjects (un pour chaque Widget qui doit être rendu)
  4. Flutter calcule les positions : le Text en haut, le Container en dessous
  5. 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.
C'est cette optimisation qui rend Flutter si performant. Flutter ne recrée que ce qui est absolument nécessaire, ce qui permet des animations fluides même sur des appareils moins puissants.

💡 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.

En tant que développeur Flutter, vous travaillerez principalement avec des Widgets. Les Elements et RenderObjects sont gérés automatiquement par le framework. Mais comprendre leur existence et leur rôle vous aidera à écrire du code plus performant et à mieux déboguer vos applications.

📚 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.
La prochaine fois que vous écrirez du code Flutter, souvenez-vous : vous créez des Widgets (descriptions), Flutter crée des Elements (gestionnaires) et des RenderObjects (rendu). Cette compréhension vous aidera à mieux structurer vos applications.