Qu’est-ce que c’est ?
Framework créé par Google en 2018, Flutter permet donc de créer des applications pour Android, iOS, le web, Linux, Windows, Mac et matériel embarqué !
Contrairement à Ionic ou Cordova, Flutter compile en natif pour les différentes plateformes.
Flutter est basé sur le langage Dart, lui aussi créé par Google.
Le but originel de l’équipe de Flutter était de fournir une meilleure manière de développer des applications pour mobile.
De quoi a-t’on besoin pour commencer ?
SDK
Il suffit d’installer le SDK Flutter en suivant le guide disponible à l’adresse https://flutter.dev/docs/get-started/install en fonction de votre environnement.
IDE
IDEs recommandés
- VSCode
- Android Studio / IntelliJ
- Plugin Flutter correspondant
Appareil cible
En fonction des plateformes ciblées, il est possible d’utiliser des appareils physiques (browser, Android phone, iPhone…) ou des émulateurs.
Est-ce que mon environnement est prêt ?
La commande flutter doctor
vérifie si tous les éléments nécessaires au développement Flutter sont disponibles et donc si votre environnement de dev est prêt.
Exemple de retour de la commande
➜ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.10.5, on Ubuntu 22.04.2 LTS 5.17.0-1033-oem, locale fr_FR.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2022.2)
[✓] Connected device (3 available)
[✓] Network resources
• No issues found!
Go !
Création du projet
La commande flutter create my_app
initalise un nouveau projet dans le dossier my_app
du répertoire courant.
Structure d’un projet Flutter
Pour commencer, voici les fichiers/dossiers importants:
android
etios
: dossiers spécifiques aux plateformeslib
: c’est ici que presque tout se passe. Le code source Dart est écrit dans ce répertoire.test
: les tests unitaires, s’il y en apubsec.yaml
: la config du projet (nom, description, dépendances…). C’est un peu l’équivalent de composer.json ou package.json.metadata
et.packages
: fichiers de config utiles au fonctionnement de Flutter, c’est tout ce qu’on a besoin de savoir pour le moment sur ces 2 fichiers.
Un petit dernier, analysis-options.yaml
, sert à informer l’IDE de la manière dont il doit analyser le code.
Widgets
Flutter permet de créer des applications avec un seul langage : Dart.
Ce langage se charge aussi bien de créer la logique métier, que de générer les interfaces nécessaires.
Tout cela est fait dans des Widgets. (les widges fournis sont disponibles ici)
Un widget est en fait une classe, qui peut avoir des propriétés et des méthodes.
D’ailleurs, chaque widget a une méthode build(BuildContext context)
.
Exemple de widget Flutter:
class RootWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello world');
}
}
Ici, on a donc un Widget RootWidget
qui étend le StatelessWidget
(voir plus loin).
Sa méthode build
renvoie un Widget
(ici le widget Text
) et reçoit un BuildContext
nommé context
en argument.
Pour lancer une application Flutter, on a besoin d’une fonction main
.
Cette fonction appellera la fonction runApp
qui reçoit un widget.
Voici comment lancer l’application avec le widget RootWidget précédent:
// import the Dart package needed for all Flutter apps
import 'package:flutter/material.dart';
// Here is main calling runApp
void main() => runApp(RootWidget());
// And here is your root widget
class RootWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello world');
}
}
Types de widgets
Valeur
Ici, ce sont des widgets qui gèrent l’affichage ou l’obtention de valeur.
En voici quelques exemples
Text
Vu plus haut, il est chargé, comme son nom l’indique, d’afficher le texte qui lui est passé en paramètre.
Text('Hello world'),
Icon / Image
Permettent d’afficher des icônes (liste ici)
Icon(
Icons.cake,
color: Colors.red,
size: 200,
)
ou des images.
TextField
Input de type texte
TextField(
onChanged: (String val) => _searchTerm = val,
),
La méthode onChanged
de ce widget est appelée à chaque modification de la valeur.
Layout
Ces widgets sont utiles pour assurer la mise en page des widgets.
MaterialApp
C’est le widget principal (mais invisible pour l’utilisateur) de mise en page, c’est ici que sont définis:
- le thème (font, colors…)
- les routes
- la page d’accueil
- …
Exemple
Widget build(BuildContext context) {
return MaterialApp(
home: MainWidget(),
title: "Ch 6 Layouts",
theme: ThemeData(primarySwatch: Colors.deepPurple),
routes: <String, WidgetBuilder>{
'/scene1: (BuildContext ctx) => MyWidget1(),
'/scene2: (BuildContext ctx) => MyWidget2(),
'/scene3: (BuildContext ctx) => MyWidget3(),
},
);
}
Scaffold
Celui-ci vient juste après MaterialApp
et fournit la structure de l’app, avec par exemple, la possibilité d’ajouter à l’app:
l'AppBar
(barre de titre de l’app)- une navigation en bas de l’écran
- un menu hamburger
- un FAB (floating action button)
- …
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(),
drawer: MyNavigationDrawer(),
body: TheRealContentOfThisPartOfTheApp(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () { /* Do things here */},
),
bottomNavigationBar: MyBottomNavBar,
);
}
SafeArea
Les écrans de smartphones ayant des angles arrondis, une barre d’état…, il se peut que certaines parties de l’app soient tronquées ou cachées.SafeArea
est là pour empêcher ça.
Il suffit d’encadrer le contenu du body par un SafeArea widget pour qu’il se charge de faire le rendu de l’app en fonction de l’écran.
State
Fonctionnement
Les widgets peuvent être Stateless
ou Stateful
.
Les widgets Stateless peuvent contenir des données, mais celles-ci ne changent pas de valeur OU ne changent pas la manière dont doit être affichée l’écran.
Par opposition, les Stateful ont des données qui changent et qui impactent l’affichage pour que celui-ci soit à jour.
Voici à quoi ressemble la base d’un widget Stateful
class Foo extends StatefulWidget {
@override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
//Private variables here are considered the 'state'
@override
Widget build(BuildContext context) {
return someWidget;
}
}
Deux classes sont nécessaires:
- la classe du widget
- publique
- étend
StatefulWidget
- celle du state
- private (underscore devant le nom)
- responsable de:
- maintenir le state
- définir la méthode
build
Le state doit être modifié:
- depuis sa classe
- via la méthode
setState
setState(() {
// Make all changes to state variables here...
_value = 42; // <-- ... Like this
});
Le changement de state amène à la re-création du widget. S’il contient lui-même des widgets, tous les éléments « enfants » seront recrées !
Passer le state vers le bas
Pour « faire descendre » le state dans l’arborescence, il est possible de déclarer une variable de classe (la classe du widget, pas celle du state).
Cette variable est ensuite accessible dans la classe du state via l’objet widget
mis à disposition par Flutter.
Exemple
class Foo extends StatefulWidget {
final String passedIn;
// Value passed in from its host
ColorValueChanger({Key key, this.passedIn}) : super(key: key);
_FooState createState() => new _FooState();
}
class _FooState extends State<Foo> {
@override
Widget build(BuildContext context) {
return Text(widget.passedIn,);
}
}
Passer le state vers le haut
Dans Flutter, il n’est pas possible de faire « remonter » des informations.
Il existe tout de même un trick pour faire en sorte qu’un widget « parent » soit informé d’une modification sur un widget « enfant ».
Il est possible de passer une fonction par référence du « parent » vers l’ »enfant ».
Ce qui fait que, lorsque l’ »enfant » appellera cette fonction, c’est en réalité la fonction du « parent » qu’il appellera.