Aller au contenu

⬅️ Retour au sommaire Flutter

Annexe D — Explications sur le programme de démonstration Hello_flutter

1. Ce que fait l’application

L’application affiche un écran contenant :

application hello_flutter

Lorsqu’on appuie sur le bouton + en bas à droite, cela incrémente le compteur.

Voici le mécanisme général que nous allons aborder pour bien comprendre le fonctionnement d’une application Flutter :

main()
   ↓
runApp()
   ↓
MyApp
   ↓
MaterialApp
   ↓
MyHomePage
   ↓
_MyHomePageState
   ↓
Scaffold
   ├── AppBar
   ├── Body
   │    └── compteur
   └── FloatingActionButton
            ↓
      _incrementCounter()
            ↓
         setState()
            ↓
          build()
            ↓
       écran actualisé

C’est déjà presque tout le fonctionnement fondamental de Flutter.


2. L’import Flutter

import 'package:flutter/material.dart';

Cette ligne importe la bibliothèque Material Design de Flutter.

Elle donne accès à des classes comme :

MaterialApp
Scaffold
AppBar
Text
Column
Center
FloatingActionButton
Icon
Colors
ThemeData

Sans cet import, Dart ne connaîtrait pas ces classes.

Pour tes étudiants Java, tu peux faire le parallèle avec :

import org.springframework.stereotype.Controller;

ou :

import java.util.List;

En Flutter :

import 'package:flutter/material.dart';

signifie en quelque sorte :

« Je veux utiliser les composants graphiques Material fournis par Flutter. »


3. Le point d’entrée

void main() {

main() est le point d’entrée du programme Dart.

C’est la première fonction exécutée lorsque l’application démarre.

Équivalent Java :

public static void main(String[] args) {
}

Ici :

void

signifie que la fonction ne retourne rien.

Donc :

void main()

peut être lu :

Fonction principale de l’application, ne renvoyant aucune valeur.


4. runApp()

runApp(const MyApp());

runApp() demande à Flutter :

« Affiche ce widget comme racine de mon application. »

On lui donne :

MyApp()

Donc l’arbre Flutter commence par :

runApp
   ↓
MyApp

runApp() attend un objet de type :

Widget

C’est une notion fondamentale :

En Flutter, presque tout ce qui apparaît à l’écran est un Widget.


5. Pourquoi const ?

const MyApp()

const indique que l’objet peut être créé comme une constante immuable.

Flutter peut donc optimiser sa création et éviter certaines reconstructions inutiles.

Tu peux dire aux étudiants :

const
=
"cet objet ne changera pas"

On retrouvera souvent :

const Text('Bonjour')

plutôt que :

Text('Bonjour')

lorsque le widget ne dépend pas de données changeantes.


6. Déclaration de MyApp

class MyApp extends StatelessWidget {

On crée une classe :

MyApp

qui hérite de :

StatelessWidget

Un StatelessWidget est un widget sans état mutable propre.

Autrement dit :

StatelessWidget
=
widget dont les données ne changent pas
pendant son existence

Exemple :

class Logo extends StatelessWidget

si le logo reste toujours identique.

À opposer à :

StatefulWidget

qui peut avoir un état qui évolue.


7. Le constructeur de MyApp

const MyApp({super.key});

C’est le constructeur de MyApp.

Version développée conceptuellement :

const MyApp({
  Key? key,
}) : super(key: key);

La syntaxe moderne :

{super.key}

est simplement plus courte.


8. À quoi sert key ?

Une Key permet à Flutter d’identifier précisément un widget dans l’arbre.

Imaginons :

Liste
├── Alice
├── Bob
└── Charlie

Puis :

Liste
├── Charlie
├── Alice
└── Bob

Les Key aident Flutter à comprendre :

« Ce widget est toujours le même objet logique, il a simplement changé de position. »

Pour une première séance, tu peux dire :

key sert à aider Flutter à identifier les widgets, notamment lorsqu’ils bougent ou sont reconstruits.

Pas besoin d’aller beaucoup plus loin immédiatement.


9. @override

Ton texte contient :

@Override

La vraie syntaxe Dart est :

@override

C’est une annotation indiquant :

« Cette méthode redéfinit une méthode héritée de la classe parente. »

Exactement comme en Java :

@Override
public String toString() {
}

Ici, MyApp hérite de StatelessWidget et redéfinit :

build()

10. La méthode build()

Widget build(BuildContext context) {

C’est une des lignes les plus importantes du programme.

build() doit retourner un :

Widget

qui décrit l’interface graphique.

Tu peux presque traduire :

Widget build(BuildContext context)

par :

« Construis-moi l’interface de ce widget dans ce contexte. »


11. BuildContext

BuildContext context

context représente la position du widget dans l’arbre des widgets.

Imagine :

MaterialApp
   ↓
Scaffold
   ↓
Column
   ↓
Text

Chaque widget se trouve quelque part dans cet arbre.

BuildContext permet notamment de rechercher des informations autour du widget :

Theme.of(context)

ou :

Navigator.of(context)

ou encore :

MediaQuery.of(context)

Tu peux dire :

Le BuildContext est l’adresse du widget dans l’arbre Flutter.

C’est simplifié, mais très pédagogique.


12. return MaterialApp

return MaterialApp(

build() retourne ici un widget :

MaterialApp

MaterialApp représente la configuration générale d’une application utilisant Material Design.

Il peut gérer notamment :

thème
navigation
langues
routes
titre
mode clair/sombre

C’est souvent l’un des widgets les plus hauts de l’application.


13. Le titre

title: 'Flutter Demo',

On passe une propriété nommée :

title

à MaterialApp.

En Dart, les paramètres nommés sont extrêmement fréquents :

MaterialApp(
  title: 'Flutter Demo',
  theme: ...,
  home: ...,
)

C’est beaucoup plus lisible que :

MaterialApp('Flutter Demo', ..., ...)

14. Le thème

theme: ThemeData(

ThemeData définit le thème graphique général de l’application.

Par exemple :

couleurs
polices
boutons
champs
barres
cartes

Cela évite de définir les couleurs individuellement partout.


15. colorScheme

colorScheme: .fromSeed(

Cette syntaxe peut surprendre.

Elle est équivalente à :

colorScheme: ColorScheme.fromSeed(

Les versions récentes de Dart permettent cette syntaxe abrégée avec point lorsque Dart connaît déjà le type attendu.

Pour enseigner au début, je montrerais d’abord la version explicite :

colorScheme: ColorScheme.fromSeed(
  seedColor: Colors.deepPurple,
),

Elle est plus facile à comprendre.

Puis montrer :

colorScheme: .fromSeed(

comme raccourci moderne.


16. seedColor

seedColor: Colors.deepPurple

On donne une couleur de base :

Colors.deepPurple

Flutter va utiliser cette couleur pour générer automatiquement une palette cohérente.

Par exemple :

deepPurple
     ↓
couleur primaire
couleur secondaire
couleur de surface
variantes claires
variantes sombres

C’est lié à Material Design 3.


17. La page d’accueil

home: const MyHomePage(
  title: 'Flutter Demo Home Page',
),

home indique quel widget constitue l’écran principal de l’application.

Donc :

MaterialApp
    ↓
home
    ↓
MyHomePage

On crée :

MyHomePage

en lui transmettant :

title: 'Flutter Demo Home Page'

18. MyHomePage devient StatefulWidget

class MyHomePage extends StatefulWidget {

Cette fois, on utilise :

StatefulWidget

Pourquoi ?

Parce que le compteur va changer :

0
1
2
3
...

L’écran possède donc un état mutable.

C’est une distinction fondamentale :

Widget Utilisation
StatelessWidget contenu qui ne change pas
StatefulWidget contenu qui peut évoluer

19. Constructeur de MyHomePage

const MyHomePage({
  super.key,
  required this.title,
});

Le constructeur reçoit deux éléments :

super.key

et :

required this.title

required signifie :

Ce paramètre est obligatoire.

Ainsi ceci fonctionne :

MyHomePage(
  title: 'Accueil',
)

mais pas :

MyHomePage()

car title est obligatoire.


20. Propriété title

final String title;

On déclare une propriété :

title

de type :

String

et marquée :

final

Cela signifie qu’une fois affectée, sa référence ne peut plus changer.

Exemple :

final String title = 'Accueil';

On ne pourra pas ensuite écrire :

title = 'Autre chose';

Les widgets Flutter sont généralement conçus pour être immutables.


21. createState()

@override
State<MyHomePage> createState() => _MyHomePageState();

C’est probablement la partie la plus étrange pour un débutant.

Un StatefulWidget est séparé en deux objets :

MyHomePage
    │
    └── configuration immuable

_MyHomePageState
    │
    └── état mutable

createState() demande :

Quel objet doit gérer l’état de MyHomePage ?

La réponse est :

_MyHomePageState()

La notation :

=>

est une fonction courte.

Ceci :

State<MyHomePage> createState() => _MyHomePageState();

équivaut à :

State<MyHomePage> createState() {
  return _MyHomePageState();
}

Très utile à expliquer aux étudiants.


22. Pourquoi _MyHomePageState commence par _ ?

_MyHomePageState

En Dart, un identifiant commençant par _ est privé à la bibliothèque.

Exemple :

_counter
_incrementCounter
_MyHomePageState

sont considérés privés.

Dart n’utilise donc pas :

private

comme Java.

Java :

private int counter;

Dart :

int _counter;

23. Classe contenant l’état

class _MyHomePageState extends State<MyHomePage> {

Cette classe contient l’état mutable associé à :

MyHomePage

C’est ici que nous allons stocker :

_counter

et les méthodes qui modifient cet état.


24. Le compteur

int _counter = 0;

On crée une variable entière :

_counter

initialisée à :

0

Comme elle commence par _, elle est privée.

On peut comparer avec Java :

private int counter = 0;

25. Méthode d’incrémentation

void _incrementCounter() {

On définit une méthode privée :

_incrementCounter

qui ne retourne rien :

void

Elle sera exécutée lorsque l’utilisateur appuie sur le bouton +.


26. setState()

setState(() {

C’est le cœur de cet exemple.

setState() signifie en pratique :

« Je vais modifier l’état. Flutter doit ensuite reconstruire l’interface concernée. »

Le mécanisme est :

clic
 ↓
_incrementCounter()
 ↓
setState()
 ↓
_counter change
 ↓
Flutter appelle build()
 ↓
nouveau Text
 ↓
écran mis à jour

27. Incrément du compteur

_counter++;

C’est équivalent à :

_counter = _counter + 1;

ou :

_counter += 1;

Donc :

0 → 1
1 → 2
2 → 3

28. Pourquoi faut-il setState() ?

Si on écrivait seulement :

_counter++;

la valeur en mémoire pourrait changer, mais Flutter ne saurait pas nécessairement qu’il doit reconstruire l’écran.

Avec :

setState(() {
  _counter++;
});

on dit explicitement :

L’état a changé, mets à jour l’interface.

C’est une notion fondamentale avant d’introduire plus tard :

Provider
Riverpod
Bloc
ChangeNotifier

29. Deuxième build()

@override
Widget build(BuildContext context) {

Cette fois, il s’agit du build() de :

_MyHomePageState

Il construit tout l’écran principal.


30. Scaffold

return Scaffold(

Scaffold signifie littéralement échafaudage.

C’est la structure standard d’un écran Material.

Il prévoit des emplacements pour :

Scaffold
├── appBar
├── body
├── drawer
├── bottomNavigationBar
├── floatingActionButton
└── ...

Dans notre application :

Scaffold
├── AppBar
├── Body
└── FloatingActionButton

Très utile comme représentation pédagogique.


31. appBar

appBar: AppBar(

On crée la barre située en haut de l’écran.

┌──────────────────────┐
│ Flutter Demo Home... │ ← AppBar
├──────────────────────┤

32. Couleur de l’AppBar

backgroundColor:
    Theme.of(context).colorScheme.inversePrimary,

Cette ligne récupère le thème actuel.

Décomposons :

Theme.of(context)

signifie :

Cherche le Theme disponible autour de ce widget.

Puis :

.colorScheme

récupère la palette.

Puis :

.inversePrimary

récupère une couleur particulière de cette palette.

Donc :

context
   ↓
Theme
   ↓
colorScheme
   ↓
inversePrimary

33. Titre de l’AppBar

title: Text(widget.title),

On affiche un widget Text.

Mais pourquoi :

widget.title

et pas simplement :

title

Parce que nous sommes dans :

_MyHomePageState

alors que title appartient à :

MyHomePage

Flutter met à disposition :

widget

qui permet d’accéder au StatefulWidget associé.

Donc :

widget.title

signifie :

Donne-moi la propriété title du MyHomePage associé à cet état.


34. Le corps de la page

body: Center(

body représente le contenu principal de l’écran.

Center centre son enfant horizontalement et verticalement.

Structure :

body
 ↓
Center
 ↓
Column

35. child

child: Column(

Beaucoup de widgets Flutter possèdent :

child

lorsqu’ils n’acceptent qu’un seul enfant.

Exemple :

Center(
  child: Text('Bonjour'),
)

Quand ils acceptent plusieurs enfants, on trouve généralement :

children

36. Column

Column(

Column dispose ses enfants verticalement.

Par exemple :

Text 1
Text 2
Bouton
Image

À l’inverse :

Row

les place horizontalement :

Text 1   Text 2   Bouton

37. mainAxisAlignment

mainAxisAlignment: .center,

Pour une Column, l’axe principal est vertical.

Donc :

mainAxisAlignment: MainAxisAlignment.center

centre les enfants verticalement.

Encore une fois :

.center

est la syntaxe abrégée moderne.

La version plus explicite est :

mainAxisAlignment: MainAxisAlignment.center,

Je recommande cette forme au début de la formation.


38. children

children: [

Column peut contenir plusieurs widgets.

On lui fournit donc une liste Dart :

[
  widget1,
  widget2,
  widget3,
]

Ici elle contient deux widgets Text.


39. Premier texte

const Text(
  'You have pushed the button this many times:',
),

C’est un texte fixe.

Comme il ne changera jamais :

const

est pertinent.


40. Affichage du compteur

Text(
  '$_counter',

Ici nous faisons de l’interpolation de chaîne.

Si :

_counter == 5

alors :

'$_counter'

devient :

5

Autre exemple :

String nom = 'Alice';

Text('Bonjour $nom')

donne :

Bonjour Alice

41. Style du compteur

style: Theme.of(context).textTheme.headlineMedium,

On récupère à nouveau le thème :

Theme.of(context)

puis :

textTheme

puis :

headlineMedium

Le compteur est donc affiché avec une typographie importante.


42. Le bouton flottant

floatingActionButton: FloatingActionButton(

FloatingActionButton correspond au fameux bouton rond Material :

      [+]

souvent placé en bas à droite.


43. onPressed

onPressed: _incrementCounter,

C’est extrêmement important.

On indique quelle fonction appeler lorsque l’utilisateur appuie sur le bouton.

Utilisateur
    ↓
clic +
    ↓
onPressed
    ↓
_incrementCounter

Remarque importante :

onPressed: _incrementCounter,

et non :

onPressed: _incrementCounter(),

Pourquoi ?

Parce qu’on transmet la référence vers la fonction, on ne l’exécute pas immédiatement.

C’est une excellente question de QCM.


44. tooltip

tooltip: 'Increment',

C’est un texte descriptif associé au bouton.

Il peut notamment être utile pour :

accessibilité
aide utilisateur
appui long
technologies d'assistance

Vu ton intérêt pour l’accessibilité, c’est un bon moment pour montrer que Flutter intègre déjà certaines informations sémantiques.


45. L’icône

child: const Icon(Icons.add),

Le bouton contient un widget :

Icon

qui utilise :

Icons.add

c’est-à-dire l’icône :

+

Icons fournit énormément d’icônes Material :

Icons.add
Icons.delete
Icons.edit
Icons.home
Icons.search
Icons.settings

46. L’arbre réel des widgets

Je montrerais absolument ceci aux étudiants :

MyApp
│
└── MaterialApp
    │
    └── MyHomePage
        │
        └── Scaffold
            │
            ├── AppBar
            │   └── Text
            │
            ├── Center
            │   └── Column
            │       ├── Text
            │       └── Text
            │
            └── FloatingActionButton
                └── Icon

C’est probablement la représentation la plus importante de la première journée Flutter.

Une interface Flutter n’est pas conçue comme une page HTML.

Elle est composée d’un arbre d’objets Widget.


47. Les trois idées essentielles du programme

Je terminerais l’explication aux étudiants avec ces trois concepts.

Tout est composé de widgets

Application
    ↓
Widget
    ↓
Widget
    ↓
Widget

Même :

texte
bouton
alignement
marge
page
barre
icône

sont généralement des widgets.

L’interface est déclarative

En Flutter, on ne dit pas :

Créer un texte
le placer en X=40
le déplacer
changer sa valeur

On décrit plutôt :

Text('$_counter')

Flutter se charge de rendre l’interface correspondant à l’état actuel.

Conceptuellement :

UI = f(état)

C’est une formule très importante :

L’interface est une fonction de l’état.

Si :

counter = 0

Flutter construit :

0

Si :

counter = 5

Flutter construit :

5

setState() indique que l’état a changé

clic
  ↓
état modifié
  ↓
setState()
  ↓
build()
  ↓
nouvelle interface

C’est le mécanisme central de ce premier exemple.


48. Version commentée que tu peux donner aux étudiants

Je réécrirais le fichier ainsi pour le cours :

// Import des composants Material Design de Flutter.
import 'package:flutter/material.dart';

// Point d'entrée de l'application Dart.
void main() {
  // Lance Flutter en utilisant MyApp comme widget racine.
  runApp(const MyApp());
}

// Widget principal de l'application.
// StatelessWidget car MyApp ne possède pas d'état mutable.
class MyApp extends StatelessWidget {

  // Constructeur constant.
  const MyApp({super.key});

  // build() décrit l'interface produite par ce widget.
  @override
  Widget build(BuildContext context) {

    // Configuration générale de l'application Material.
    return MaterialApp(

      // Nom logique de l'application.
      title: 'Flutter Demo',

      // Thème graphique global.
      theme: ThemeData(

        // Création automatique d'une palette depuis une couleur.
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
        ),
      ),

      // Premier écran affiché.
      home: const MyHomePage(
        title: 'Flutter Demo Home Page',
      ),
    );
  }
}

// Écran possédant un état modifiable.
class MyHomePage extends StatefulWidget {

  const MyHomePage({
    super.key,
    required this.title,
  });

  // Titre immuable reçu par le constructeur.
  final String title;

  // Création de l'objet qui contiendra l'état.
  @override
  State<MyHomePage> createState() {
    return _MyHomePageState();
  }
}

// État associé à MyHomePage.
class _MyHomePageState extends State<MyHomePage> {

  // Variable d'état.
  int _counter = 0;

  // Méthode appelée lorsque l'utilisateur appuie sur +.
  void _incrementCounter() {

    // Indique à Flutter que l'état va changer.
    setState(() {

      // Incrémente le compteur.
      _counter++;
    });
  }

  // Construction de l'écran.
  @override
  Widget build(BuildContext context) {

    // Structure générale d'un écran Material.
    return Scaffold(

      // Barre supérieure.
      appBar: AppBar(

        // Couleur provenant du thème.
        backgroundColor:
            Theme.of(context).colorScheme.inversePrimary,

        // widget permet d'accéder au StatefulWidget associé.
        title: Text(widget.title),
      ),

      // Contenu principal de l'écran.
      body: Center(

        // Organisation verticale.
        child: Column(

          // Centre verticalement les enfants.
          mainAxisAlignment: MainAxisAlignment.center,

          // Widgets contenus dans la colonne.
          children: [

            // Texte fixe.
            const Text(
              'You have pushed the button this many times:',
            ),

            // Valeur actuelle du compteur.
            Text(
              '$_counter',

              // Style provenant du thème.
              style:
                  Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),

      // Bouton flottant en bas à droite.
      floatingActionButton: FloatingActionButton(

        // Fonction exécutée au clic.
        onPressed: _incrementCounter,

        // Description du bouton.
        tooltip: 'Increment',

        // Icône +.
        child: const Icon(Icons.add),
      ),
    );
  }
}

Cette version est, à mon avis, meilleure pédagogiquement que le fichier généré automatiquement, notamment parce que j’ai remplacé les raccourcis récents :

.fromSeed
.center

par leurs formes explicites :

ColorScheme.fromSeed
MainAxisAlignment.center

Pour une première journée Flutter, elles permettent aux étudiants de mieux comprendre le type auquel appartient chaque valeur avant de leur montrer les raccourcis syntaxiques modernes.

⬅️ Retour au sommaire Flutter