Flutter


Kit de développement multiplateforme créé par Google utilisant le language Dart (créé par Google aussi).
Permet de développer en même temps, avec une seule base de code, pour MacOs, Windows, Linux, iPhone, Android, web.... car Dart sera compilé spécifiquement en code natif (via les dossier windows, ios, android...) pour notre cible (dans le dossier "build").
Flutter est à la fois un framework pour créer des UI en Dart et une collection d'outils pour faire tourner notre code sur les autres plateformes.

Attention, on ne peut tester/lancer nos applis MacOs/iOs qu'à partir de MacOs, nos applis Windows que depuis Windows et nos applis Linux que depuis Linux. On peut tester/lancer nos applis Android (via émulateur) et Web de partout.

Installation


https://docs.flutter.dev/install/quick

Globalement :
-installer Git (Flutter utilise lui-même Git, un "flutter upgrade" est en fait un Git pull)
-dézipper le zip de Flutter
-ajouter flutter\sdk\flutter\bin au PATH.
-"flutter doctor" sur une invite de commande doit montrer "flutter" avec une coche verte (mais il n'y a pas encore de plateforme visée, ça sera en rouge).

Premier lancement


Dans n'importe quel dossier : flutter create ma_belle_app_flutter va créer une quinzaine de fichiers/dossiers.

Concepts



Widget


Quasi tout est widget (l'appli elle même, les layouts, les boutons etc).

Il est très rapide de détruire et recréer un widget (60 fois par seconde sans problème). On peut détruire/reconstruire le widget parent (par exemple changer la couleur de l'app) sans détruire le widget enfant. Il est assez intelligent pour ne pas recréer un widget si rien n'a changé.

-Les Widgets sont immuables, ce sont des classes (donc avec méthodes, attributs...).

-Certains sont visibles, d'autres invisibles (servent à stocker des données, d'autres widgets, faire du layout...).

-Certains héritent de StatelessWidget, d'autres StatefulWidget (car doivent mémoriser des choses un champ texte retiendra la position du curseur, la position de la scrollbar...).

-Un StatelessWidget doit absolument implémenter une méthode "build", qui retourne un Widget, sera appelée à chaque création du Widget. La méthode DOIT contenir des sous widgets qui sont déclarés dans la méthode Build (vu que la méthode est obligatoire et DOIT renvoyer un Widget...).

-Un StatefulWidget doit absolument contenir une méthode createState. C'est dans le State qu'on trouvera la méthode "build". Le state est conservé, par contre "build" peut être appelé plusieurs fois par secondes sans souci :

-Certains widgets sont dans les faits stateless mais n'héritent pas de StatelessWidget car il font du rendu (héritent de RenderObjectWidget). Ils sont le fond du fond, c'est à dire quasiment les pixels qu'on va écrire sur l'écran, on arrive au bout de l'arbre là. Comme ils n'héritent pas de StatelessWidget ils n'ont pas besoin d'implémenter de méthode Build.

Appli minimale



import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(home: Text("coucou"))); }


Application exemple en détail


Ci-dessous une analyse perso de l'appli exemple donnée par Google en 2026.


void main() { //Première fonction appelée runApp(MyApp()); // fonction du framework Flutter, prend un widget en entrée }


Au moment de la création d'un widget, la méthode build est appelée :

class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { print("build appelé"); // S'appellera une seule fois return ChangeNotifierProvider( // C'est (le constructeur d') un widget (a "Widget" pour ancêtre), est invisible, informe ses enfants de changements d'états. create: (context) => MyAppState(), //L'état de l'application. Sera transmis aux enfants. child: MaterialApp( //ici on créé un autre widget, invisible, qui transmettra des infos de config visuelles (thème, bavigation, police d'écriture...). title: 'Namer App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), ), home: MyHomePage(), //La route par défaut de MaterialApp ), ); } }



En dessous on peut avoir le ChangeNotifier :

class MyAppState extends ChangeNotifier { // Ce n'est pas un widget. Hérite de Listenable. Notifie ses abonnés en cas de changement. var current = WordPair.random(); // Il préviendra si on modifie ses attributs void coucouIciLeChangeNotifier() { // On peut lui mettre une fonction que ses abonnés (enfant de celui qui nous contient) peuvent appeler. print('ici le change notifier, coucou !'); print( WordPair.random()); current = WordPair.random(); notifyListeners(); // Il faut absolument dire aux abonnés qu'on a changé un truc (concrètement, va voir dans sa liste de callback). } }


Et enfin le widget "page principale" :


class MyHomePage extends StatelessWidget // Un autre Widget invisible qui réunit les Widgets de la page d'accueil. @override Widget build(BuildContext context) { // Sera appelé quand appState change. Context en entrée fait le lien avec le reste de l'app en donnant le contexte dans lequel est créé cet élément. var appState = context.watch<MyAppState>(); // On "s'abonne" à MyAppState avec cette variable, mais en vérité c'est l'inverse : cette référence au contexte vient s'enregistrer en tant que callback dans une liste du ChangeNotifier. // Pour chaque élément, dans le framework on a un "Widget build() => widget.build(this);", et "this" représente le contexte de l'élément en cours. return Scaffold( // Un widget (Stateful -car mémorise "le menu latéral est-il ouvert", "le clavier déplié" etc- mais osef). Un échauffadage pour faire rapidement des structures/visuelles correspondant aux règles de design Google (couleur de fond, positionnements...). Va générer automatiquement des widgets visuels selon la config qu'on lui donne. backgroundColor: Color.fromARGB(255, 0, 255, 0), // pour configurer le Widget (de type Material) que va générer Scaffold body: Column( //Encore un widget invisible, définit les layout. children: [ // Scaffold va générer un widget pour le font mais on peut lui donner nos propres widgets Text('Une idée au pif :'), //Ce Widget là sera visible, il utilise RichText qui est un RenderObjectWidget. Text(appState.current.asLowerCase), ElevatedButton( // Un widget stateful (est-il pressé, survolé...) onPressed: () { print('button pressed!'); appState.coucouIciLeChangeNotifier(); //On appelle le state contenu par le widget ChangeNotifierProvider, qui est notre arrière grand père. }, child: Text('Next'), ), ], ), ); } }


L'arbre (widget tree) sera le suivant :
-MyApp (Le widget racine)
--ChangeNotifierProvider (Un Widget invisible, contient l'état "MyAppState")
---MaterialApp (Widget pour configurer le visuel).
----MyHomePage (Widget invisible qui regroupe les widgets de la home page).
-----Scaffold
------(plein de widgets "par défaut" genre la direction du texte, le fond...)
------Column
------Text
------Text
------ElevatedButton
-------Text

Quand on clique sur le boutton ElevatedButton, il va déclencher la fonction contenue dans onPressed, faire le print et appeler le parent.


Il y a 3 arbres dans Flutter, donc on peut détruire un Widget parent :
-arbre des widgets (éphémère, contient la description de l'UI, c'est une recette)
-arbre des éléments (persistent, contient les States et le BuildContext -la position dans l'arbre et permet de chercher des éléments, se souvient de ce qui existait avant)
-arbre du rendu (gère le layout, le rendu, les interactions).


Le widget stateful, la syntaxe est un peu chelou :

class MyHomePage extends StatefulWidget { @override State<MyHomePage> createState() => _MyHomePageState(); // on peut pas direct avoir le build là, un peu WTF. } class _MyHomePageState extends State<MyHomePage> { //il faut cette sous classe, car elle est immuable et sera détruire plein de fous par secondes. var indexChoisi = 0; // @override void initState() { //Par contre ça c'est appelé qu'une fois, pratique si on veut faire une action en début de widget. super.initState(); print("coucou"); } @override Widget build(BuildContext context) { print("build myhomepage appelé"); var appState = context.watch<MyAppState>(); return Text("coucou"); }


Faire ses propres widgets


On peut créer son propre Widget qui sera une combinaison de plusieurs widgets, particulièrement quand le contenu du du Widget tree est trop long. Pour ça, une classe qui extends StatelessWidget ou StatefulWidget avec une méthode build retournant la combinaison de Widgets qu'on veut utiliser.

Exemple :

void main() {runApp(MaterialApp(home: Scaffold(body: MonPetitWidget(),),),);} class MonPetitWidgetextends StatelessWidget { @override Widget build(BuildContext context) { return Text("coucou");} }


Constructeurs


Il y a plusieurs types de constructeurs en Dart.

Pour un widget on utilise général le constructeur qui fonctionne avec const :

class GrosseCarte extends StatelessWidget { final WordPair pair; // un attribut, sera alimenté par le constructeur. const GrosseCarte({ // Le constructeur ! Avec const devant pour l'optimiser et le garder en mémoire. super.key, // c'est une sorte d'id unique au composant, super indique qu'on remonte ce qu'on reçoit auto au parent. required this.pair, //Ici c'est un paramètre qu'on donne en entrée du constructeur, sera stocké dans this.pair. //En Java on aurait genre "this.pair=param"... sauf que tout est implicite ici. }); //ici on n'a pas d'accolade après le constructeur car il est vide. En Java on aurait accolades vides. @override Widget build(BuildContext context) { return Text(pair.asLowerCase); // } }


Si on utilise l'ancienne syntaxe ou si on veut ajouter du code dans le constructeur,
on ne peut plus utiliser le mot-clé "const" et on doit écrire un corps :

GrosseCarte({ Key? key, required this.pair, }) : super(key: key) { // ici c'est aussi une ancienne syntaxe, indique qu'on veut remonter l'info key en appelant le constrtucteur du parent ("initializer list") print("La carte a été créée avec la valeur : $pair"); //là on est dans le corps du constructeur }


Vrac


const : permet à Flutter de garder un élément en mémoire et de le réutiliser plus tard vu qu'on lui dit que c'est une constante et qu'il ne change pas.

Type


Tous les types héritent de Object (int hérite de num qui hérite d'object). Un Widget est un Object. Et un Object est une structure de données. Même les "tableaux" [a,b,c] sont en fait des listes.

Garder les virgules pour le formattage


Dans "analysis_options.yaml" :

formatter: trailing_commas: preserve


Named parameters




maBelleFonction(nomDeLArgument: valeur, nomDeLAutreArgument:valeur2); void maBelleFonction({nomDeLArgument, nomDeLAutreArgument}) {print(nomDeLArgument, nomDeLAutreArgument);}


Test en debug sur son téléphone sans installer Android Studio


-télécharger les https://developer.android.com/studio#command-tools
-rajouter un dossier "latest" avant bin, le chemin doit être "C:\outils\androidsdk\cmdline-tools\latest\bin".
-faire .\sdkmanager.bat "platforms;android-36" "build-tools;36.0.0" "build-tools;28.0.3" "platform-tools"
-vérifier que tout est vert en faisant flutter doctor, si besoin taper ce que dit le docteur (licenses...).
-connecter son téléphone en mode usb debugging (faut être en mode dev)
-faire flutter run, ça va être long car il va télécharger plein de libs dans .gradale.