1. Introduction
Flutter est un kit d'interface utilisateur (UI) Google qui permet de créer des applications pour les mobiles, le Web et les ordinateurs à partir un même codebase. Dans cet atelier de programmation, vous allez développer l'application Flutter suivante :
L'application génère des noms chantants, comme "newstay", "lightstream", "mainbrake" ou "graypine". L'utilisateur peut accéder au nom suivant, mettre le nom affiché en favori et consulter la liste de ses noms préférés dans une autre page. L'application est responsive aux différentes tailles d'écran.
Points abordés
- Fonctionnement de base de Flutter
- Créer des mises en page sous Flutter
- Associer les interactions utilisateur (appuis de bouton, par exemple) aux comportements de l'application
- Assurer l'organisation du code Flutter
- Rendre l'application responsive (aux différents écrans)
- Proposer une interface homogène
Dans un premier temps, vous allez créer une structure de base vous permettant de passer directement aux parties intéressantes.
Voici Filip qui vous accompagnera tout au long de cet atelier de programmation.
Cliquez sur Suivant pour lancer l'atelier.
2. Configurer votre environnement Flutter
Éditeur
Pour que cet atelier de programmation soit le plus facile possible, nous avons présumé que vous alliez utiliser Visual Studio Code (VS Code) comme environnement de développement. Il est gratuit et fonctionne sur la plupart des plates-formes.
Vous pouvez bien sûr utiliser l'éditeur de votre choix, comme Android Studio, les IDE IntelliJ, Emacs, Vim ou Notepad++. Tous fonctionnent avec Flutter.
Nous recommandons d'utiliser VS Code pour cet atelier de programmation, car les instructions renvoient par défaut aux raccourcis de VS Code. Il est plus simple d'indiquer "cliquez ici" ou "appuyez sur cette touche" (plutôt que : "effectuez l'action correspondante dans votre éditeur pour X".
Choisir une cible de développement
Flutter est un kit multiplate-forme. Votre application peut s'exécuter sur l'un des systèmes d'exploitation suivants :
- iOS
- Android
- Windows
- macOS
- Linux
- Web
Il est cependant courant de choisir un système d'exploitation sur lequel vous allez principalement fonder votre développement. Il s'agit de la "cible de développement", à savoir le système d'exploitation sur lequel l'application s'exécute pendant le développement.
Par exemple, supposons que vous utilisiez un ordinateur portable Windows pour développer une application Flutter. Si vous choisissez Android comme cible de développement, vous allez associer un appareil Android à l'ordinateur portable Windows par câble USB. Ainsi, l'application en cours de développement va s'exécuter sur cet appareil Android. Vous pouvez aussi choisir Windows comme cible de développement : l'application en cours de développement s'exécute comme une application Windows, en parallèle de l'éditeur.
Vous pourriez avoir envie de choisir le Web comme cible de développement. Mais dans ce cas, vous perdez l'une des fonctionnalités de développement les plus utiles de Flutter, le hot reload avec état. Flutter ne peut pas procéder au hot reload des applications Web.
Vous devez faire votre choix dès maintenant. Sachez que vous pourrez toujours exécuter votre application sur d'autres systèmes d'exploitation par la suite. Mais définir clairement sa cible de développement permet de simplifier la prochaine étape.
Installer Flutter
Les dernières instructions pour installer le SDK Flutter sont toujours disponibles sur docs.flutter.dev.
Les instructions du site Web Flutter couvrent non seulement l'installation du SDK, mais aussi les outils associés à la cible de développement et les plug-ins de l'éditeur. Sachez que pour cet atelier de programmation, il vous suffit d'installer les éléments suivants :
- SDK Flutter
- Visual Studio Code avec plug-in Flutter
- Le logiciel requis par la cible de développement retenue (par exemple, Visual Studio pour Windows ou Xcode pour macOS)
Dans la section suivante, vous allez créer votre premier projet Flutter.
Si vous avez rencontré un problème jusqu'ici, vous pourriez trouver certaines des questions-réponses ci-dessous (sur StackOverflow) utiles à des fins de dépannage.
Questions fréquentes
- Comment obtenir le chemin d'accès au SDK Flutter ?
- Que faire lorsqu'une commande Flutter est introuvable ?
- Comment résoudre le problème "Waiting for another Flutter command to release the startup lock" (En attente d'une autre commande Flutter pour débloquer le verrouillage au démarrage) ?
- Comment indiquer à Flutter où se trouve le répertoire d'installation du SDK Android ?
- Comment résoudre l'erreur Java lors de l'exécution de
flutter doctor --android-licenses
? - Que faire lorsque l'outil
sdkmanager
d'Android est introuvable ? - Comment résoudre l'erreur "
cmdline-tools
component is missing" (Le composantcmdline-tools
est manquant) ? - Comment exécuter CocoaPods sur Apple Silicon (M1) ?
- Comment désactiver le formatage automatique à l'enregistrement dans VS Code ?
3. Créer un projet
Créer votre premier projet Flutter
Lancez Visual Studio Code et ouvrez la palette de commandes (avec F1
, Ctrl+Shift+P
ou Shift+Cmd+P
). Saisissez "flutter new". Sélectionnez la commande Flutter: New Project (Flutter : nouveau projet).
Sélectionnez ensuite Application et indiquez le dossier dans lequel créer votre projet. Il peut s'agir de votre répertoire d'accueil ou d'un élément comme C:\src\
.
Enfin, attribuez un nom à votre projet, comme namer_app
ou my_awesome_namer
.
Flutter crée le dossier de votre projet et VS Code l'ouvre.
Vous allez maintenant remplacer le contenu des trois fichiers et créer la structure de base de l'application.
Copier et coller l'application d'origine
Dans le volet de gauche de VS Code, vérifiez qu'Explorer (Explorateur) est sélectionné et ouvrez le fichier pubspec.yaml
.
Remplacez le contenu de ce fichier par le code ci-dessous :
pubspec.yaml.
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: '>=2.19.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
Le fichier pubspec.yaml
définit les informations de base de votre application, comme sa version actuelle, ses dépendances et les éléments utilisés pour son implémentation.
Ouvrez ensuite un autre fichier de configuration dans le projet, analysis_options.yaml
.
Remplacez le contenu par ceci :
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: false
prefer_final_fields: false
use_key_in_widget_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_const_constructors_in_immutables: false
avoid_print: false
Le fichier détermine la rigueur de Flutter lorsqu'il analyse votre code. Puisqu'il s'agit d'une entrée en matière à Flutter, vous allez indiquer à l'analyseur d'y aller doucement. Vous pourrez toujours personnaliser cela par la suite. En réalité, vous aurez certainement envie d'augmenter la rigueur de l'analyseur à mesure que vous approcherez de la publication de la véritable application de production.
Enfin, ouvrez le fichier main.dart
dans le répertoire lib/
.
Remplacez le contenu de ce fichier par le code ci-dessous :
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
Jusqu'ici, ces 50 lignes de code constituent l'intégralité de l'application.
Dans la section suivante, vous allez exécuter l'application en mode débogage et commencez le développement.
4. Ajouter un bouton
Cette étape vise à ajouter un bouton Next (Suivant) pour générer une paire de mots.
Lancer l'application
Tout d'abord, ouvrez lib/main.dart
et assurez-vous que l'appareil cible est sélectionné. Un bouton en bas à droite de VS Code indique quel est l'appareil cible actuel. Cliquez dessus pour le modifier.
Avec lib/main.dart
ouvert, recherchez le bouton de lecture en haut à droite de la fenêtre de VS Code et cliquez dessus.
Après une minute environ, l'application se lance en mode débogage. Pour le moment, elle ne ressemble pas à grand-chose :
Premier hot reload
En bas de lib/main.dart
, ajouter un élément à la chaîne du premier objet Text
et enregistrez le fichier (avec Ctrl+S
ou Cmd+S
). Par exemple :
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Notez que l'application change instantanément, mais que le mot aléatoire reste le même. C'est le fameux hot reload avec état de Flutter. Le hot reload se déclenche lorsque vous enregistrez des modifications dans un fichier source.
Questions fréquentes
- Que faire si le hot reload ne fonctionne pas dans VS Code ?
- Dois-je appuyer sur "r" pour lancer un hot reload dans VS Code ?
- Le hot reload fonctionne-t-il sur le Web ?
- Comment retirer la bannière "Debug" (Débogage) ?
Ajouter un bouton
Ajoutez ensuite un bouton en bas de Column
, juste en dessous de la seconde instance Text
.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
Lorsque vous enregistrez la modification, l'application s'actualise de nouveau et un bouton apparaît. Lorsque vous cliquez dessus, la Debug Console (Console de débogage) de VS Code affiche le message button pressed! (Bouton actionné).
Cours d'initiation à Flutter en 5 minutes
Bien qu'il soit agréable de consulter la Debug Console (Console de débogage), vous souhaitez que le bouton ait une action plus concrète. Pour cela, vous devez étudier de près le code dans lib/main.dart
afin d'en comprendre le fonctionnement.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
La fonction main()
se trouve tout en haut du fichier. Dans sa forme actuelle, elle ne fait qu'indiquer à Flutter d'exécuter l'application définie dans MyApp
.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
La classe MyApp
étend le StatelessWidget
. Les widgets sont les éléments que vous devez utiliser pour développer une application Flutter. Notez que l'application elle-même est un widget.
Le code dans MyApp
configure l'ensemble de l'application. Il crée l'état au niveau de l'application (vous en saurez plus par la suite), nomme l'application, définit le thème visuel et configure le widget "home" (Accueil) (le point de départ de votre application).
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Ensuite, la classe MyAppState
définit l'état d'intégrité de l'application. Puisqu'il s'agit d'une entrée en matière à Flutter, cet atelier de programmation se veut simple et précis. Il existe de nombreuses méthodes efficaces pour gérer l'état d'une application dans Flutter. L'une des plus simples est l'approche ChangeNotifier
que nous allons adopter pour cette application.
MyAppState
détermine les données dont l'application a besoin pour fonctionner. Pour le moment, elle ne comporte qu'une seule variable avec l'actuelle paire de mots aléatoires. Vous la compléterez ultérieurement.- La classe d'état étend
ChangeNotifier
qui peut alors informer les autres widgets de ses propres modifications. Par exemple, certains widgets de l'application doivent être informés en cas de modification de l'actuelle paire de mots. - L'état est créé et transmis à l'ensemble de l'application avec un
ChangeNotifierProvider
(voir le code ci-dessus dansMyApp
). Cela permet de communiquer l'état à tous les widgets de l'application.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
Enfin, vous avez le widget MyHomePage
que vous avez déjà modifié. Chaque puce numérotée ci-dessous correspond à un commentaire avec numéro de ligne dans le code ci-dessus :
- Chaque widget définit une méthode
build()
automatiquement appelée dès que les conditions du widget changent, de sorte qu'il soit toujours à jour. MyHomePage
suit les modifications de l'état actuel de l'application avec la méthodewatch
.- Chaque méthode
build
doit renvoyer un widget ou (plus généralement) une arborescence de widgets imbriquée. Dans ce cas, le widget de premier niveau estScaffold
. Dans cet atelier de programmation, vous n'allez pas utiliserScaffold
. Néanmoins, ce widget est pratique et utilisé dans la plupart des applications Flutter du monde réel. Column
est l'un des principaux widgets de mise en page de Flutter. Il accepte un nombre illimité d'enfants et les place dans une colonne, de haut en bas. Par défaut, la colonne place visuellement ses enfants en haut. Vous allez apprendre à modifier cela pour que la colonne soit centrée.- Vous avez modifié le widget
Text
à la première étape. - Ce second widget
Text
accepteappState
et accède au seul membre de la classe,current
(qui est uneWordPair
).WordPair
fournit plusieurs getters utiles, commeasPascalCase
ouasSnakeCase
. Dans notre cas, nous utilisonsasLowerCase
. Vous pouvez modifier cela si vous préférez l'une des autres solutions. - Notez que le code Flutter utilise massivement les virgules de fin. Ce type de virgule n'est pas nécessaire ici, car
children
est le dernier (et le seul) membre de cette liste de paramètresColumn
spécifiques. L'utilisation des virgules de fin est généralement adaptée : elle simplifie l'ajout de membres et sert de repère lorsque le formateur automatique Dart doit insérer une nouvelle ligne. Pour en savoir plus, consultez Code formatting (Formatage du code).
Vous allez maintenant connecter le bouton à l'état.
Votre premier comportement
Faites défiler jusqu'à MyAppState
et ajoutez une méthode getNext
.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
La nouvelle méthode getNext()
réattribue current
à une nouvelle WordPair
aléatoire. Elle appelle également notifyListeners()
(une méthode de ChangeNotifier)
qui garantit que toute personne surveillant MyAppState
est informée.
Il ne reste plus qu'à appeler la méthode getNext
depuis le rappel du bouton.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Enregistrez l'application et lancez-la. Elle doit générer une paire de mots aléatoires à chaque fois que vous appuyez sur le bouton Next (Suivant).
Dans la section suivante, vous allez embellir l'interface utilisateur.
5. Embellir l'application
Voici à quoi ressemble l'application pour le moment.
Ce n'est pas l'idéal. La pièce maîtresse de l'application (la paire de mots générée de façon aléatoire) doit être plus visible. Après tout, elle est ce qui incite les utilisateurs à exécuter l'application. Par ailleurs, le contenu est bizarrement décentré et l'application est globalement ennuyeuse, tout en noir et blanc.
Dans cette section, vous allez corriger ces anomalies en travaillant sur le graphisme de l'application. L'objectif final est d'obtenir un rendu semblable à celui-ci :
Extraire un widget
La ligne permettant d'afficher l'actuelle paire de mots ressemble maintenant à cela : Text(appState.current.asLowerCase)
. Pour obtenir un rendu plus complexe, il est recommandé de l'extraire dans un widget séparé. Utiliser des widgets séparés pour les différentes composantes logiques de l'UI offre un excellent moyen de gérer la complexité dans Flutter.
Flutter fournit un assistant de refactorisation pour extraire les widgets. Avant de l'utiliser, assurez-vous que la ligne à extraire accède uniquement à ce dont elle a besoin. Pour le moment, la ligne accède à appState
. En réalité, elle a seulement besoin de connaître l'actuelle paire de mots.
C'est pourquoi vous devez réécrire le widget MyHomePage
comme ceci :
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Super. Le widget Text
ne fait plus référence à appState
dans son ensemble.
Appelez maintenant le menu Refactor (Refactorisation). Dans VS Code, vous pouvez employer l'une des deux méthodes suivantes :
- Effectuez un clic droit sur l'extrait de code que vous souhaitez refactoriser (
Text
, dans le cas présent) et sélectionnez Refactor… (Refactoriser) dans le menu déroulant.
OU
- Déplacez votre curseur sur l'extrait de code que vous souhaitez refactoriser (
Text
, dans le cas présent) et appuyez surCtrl+.
(Windows/Linux) ou surCmd+.
(Mac).
Dans le menu Refactor (Refactorisation), sélectionnez Extract Widget (Extraire le widget). Donnez-lui un nom (comme BigCard), puis cliquez sur Enter
.
Cela crée automatiquement une nouvelle classe (BigCard
) à la fin du fichier actuel. La classe doit ressembler à ceci :
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
Notez que la classe continue de fonctionner même pendant la refactorisation.
Ajouter une carte
Vous allez maintenant faire de ce nouveau widget la pièce maîtresse de l'UI telle que nous l'avons imaginée au début de la section.
Identifiez la classe BigCard
et la méthode build()
qu'il contient. Comme précédemment, appelez le menu Refactor (Refactorisation) dans le widget Text
. Cette fois-ci, vous n'allez pas extraire le widget.
Sélectionnez plutôt Wrap with Padding (Encapsuler avec une marge intérieure). Cela crée un widget parent appelé Padding
autour du widget Text
. Après avoir enregistré, notez que le mot aléatoire dispose plus d'espace.
Augmentez la valeur par défaut de la marge intérieure de 8.0
. Par exemple, utilisez une valeur comme 20
pour une marge intérieure plus grande.
Passons maintenant au niveau supérieur. Placez votre curseur sur le widget Padding
, affichez le menu Refactor (Refactorisation) et sélectionnez Wrap with widget… (Encapsuler avec un widget).
Cela vous permet de spécifier le widget parent. Saisissez "Card" et appuyez sur Enter (Entrée).
Cela encapsule le widget Padding
ainsi que le Text
avec un widget Card
.
Thème et style
Pour que la carte ressorte plus clairement, donnez-lui une couleur plus riche. Il est recommandé de conserver un jeu de couleurs homogène : utilisez le Theme
de l'application pour choisir une couleur.
Apportez les modifications suivantes à la méthode build()
de BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
Ces deux nouvelles lignes réalisent une grande part du travail :
- Tout d'abord, le code demande le thème actuel de l'application avec
Theme.of(context)
. - Ensuite, le code définit la couleur de la carte de sorte qu'elle soit identique à la propriété
colorScheme
du thème. Le jeu de couleurs comporte de nombreuses couleurs,primary
étant la couleur prédominante qui définit l'application.
La carte apparaît désormais dans la couleur principale de l'application :
Pour changer cette couleur et le jeu de couleurs de l'application dans son ensemble, faites défiler vers le haut jusqu'à MyApp
et remplacez la couleur source de ColorScheme
.
Notez la fluidité avec laquelle la couleur s'anime. Cela s'appelle une animation implicite. De nombreux widgets Flutter offrent une interpolation fluide des valeurs, de sorte que l'UI ne "saute" pas d'un état à un autre.
Le bouton surélevé sous la carte change également de couleur. C'est là tout l'avantage d'utiliser un Theme
au niveau de l'application plutôt que des valeurs codées en dur.
TextTheme
La carte présente toujours un problème : le texte est trop petit et sa couleur le rend peu lisible. Pour corriger cela, apportez les modifications suivantes à la méthode build()
de BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
Ces modifications apportent les améliorations suivantes :
- Utiliser
theme.textTheme,
permet d'accéder au thème des polices. Cette classe comprend des membres commebodyMedium
(pour le texte standard de taille moyenne),caption
(pour les captures d'images) ouheadlineLarge
(pour les gros titres). - La propriété
displayMedium
est un grand style prévu pour le titrage du texte. Le terme titrage est ici utilisé dans le sens typographique, comme pour caractère de titrage. La documentation dedisplayMedium
indique que "les styles de titrage sont réservés aux textes courts et importants" (ce qui correspond précisément à notre cas). - En théorie, la propriété
displayMedium
du thème peut êtrenull
. Dart (le langage de programmation utilisé pour écrire cette application) est null-safe et ne permet donc pas d'appeler les méthodes d'objets potentiellementnull
. Dans ce cas, vous pouvez toutefois utiliser l'opérateur!
("opérateur bang") pour indiquer à Dart que vous savez ce que vous faites. (displayMedium
n'est réellement pas null dans notre cas. C'est pourquoi cela ne relève pas de cet atelier de programmation.) - Appeler
copyWith()
surdisplayMedium
renvoie une copie du style de texte avec les modifications que vous avez apportées. Dans ce cas, vous n'avez fait que modifier la couleur du texte. - Pour obtenir la nouvelle couleur, accédez de nouveau au thème de l'application. La propriété
onPrimary
du jeu de couleurs définit une couleur adaptée pour apparaître au-dessus de la couleur principale de l'application.
L'application doit maintenant ressembler à ceci :
Si vous en avez envie, embellissez un peu plus la carte. Voici quelques idées :
copyWith()
permet de modifier davantage le style de texte (au-delà de la couleur). Pour obtenir la liste complète des propriétés que vous pouvez modifier, placez votre curseur entre les parenthèses decopyWith()
et appuyez surCtrl+Shift+Space
(Windows/Linux) ouCmd+Shift+Space
(Mac).- De même, vous pouvez modifier un peu plus le widget
Card
. Par exemple, vous pouvez étendre l'ombre de la carte en augmentant la valeur du paramètreelevation
. - Jouez avec les couleurs. En plus de
theme.colorScheme.primary
, il existe une myriade de possibilités, dont.secondary
et.surface
. Toutes ces couleurs ont leurs équivalentsonPrimary
.
Améliorer l'accessibilité
Flutter assure l'accessibilité des applications par défaut. Par exemple, chaque application Flutter présente correctement l'ensemble du texte et des éléments interactifs intégrés aux lecteurs d'écran tels que TalkBack et VoiceOver.
Cependant, certaines modifications peuvent être nécessaires. Dans le cas de notre application, le lecteur d'écran peut avoir du mal à prononcer certaines paires de mots générées. Là où un être humain n'a aucun problème à identifier les deux mots contenus dans cheaphead, un lecteur d'écran peut prononcer les lettres ph au milieu d'un mot comme un f.
Une solution simple consiste à remplacer pair.asLowerCase
par "${pair.first} ${pair.second}"
. Ce dernier utilise l'interpolation de chaîne pour créer une chaîne (comme "cheap head"
) à partir des deux mots contenus dans pair
. En utilisant deux mots séparés (plutôt qu'un mot composé), vous êtes assuré que les lecteurs d'écran les identifient correctement. En outre, cela améliore l'expérience utilisateur des déficients visuels.
Vous pouvez cependant avoir envie de conserver la simplicité visuelle de pair.asLowerCase
. Utilisez la propriété semanticsLabel
de Text
pour remplacer le contenu visuel du widget "text" par un contenu sémantique adapté aux lecteurs d'écran :
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
Maintenant, les lecteurs d'écrans prononcent correctement chaque paire de mots générée sans que l'UI ait changé. Vérifier cela en utilisant un lecteur d'écran sur votre appareil.
Centrer l'UI
La présentation visuelle de la paire de mots aléatoires est aboutie. Le moment est venu de la placer au centre de la fenêtre ou de l'écran de l'application.
Tout d'abord, rappelez-vous que BigCard
fait partie de Column
. Par défaut, les colonnes regroupent leurs enfants en haut. Nous pouvons facilement modifier cela. Accédez à la méthode build()
de MyHomePage
et apportez la modification suivante :
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Cela permet de centrer l'enfant dans la Column
, le long de l'axe principal (verticalement).
Les enfants sont déjà centrés le long de l'axe perpendiculaire (à savoir horizontalement). Cependant, la Column
elle-même n'est pas centrée dans le Scaffold
. Pour le vérifier, nous pouvons utiliser le Widget Inspector (Outil d'inspection de widgets).
Le Widget Inspector (Outil d'inspection de widgets) ne relève pas du présent atelier de programmation. Notez que lorsque la Column
est en surbrillance, elle n'occupe pas toute la largeur de l'application. Elle n'utilise que l'espace horizontal dont ses enfants ont besoin.
Vous pouvez simplement centrer la colonne. Placez votre curseur sur Column
, appelez le menu Refactor (Refactorisation) (avec Ctrl+.
ou Cmd+.
) et sélectionnez Wrap with Center (Encapsuler avec le centre).
L'application doit maintenant ressembler à ceci :
Si vous le souhaitez, vous pouvez affiner l'opération.
- Vous pouvez supprimer le widget
Text
au-dessus deBigCard
. Nous pouvons affirmer que le texte descriptif ["A random AWESOME idea:" (Une idée grandiose et aléatoire)] n'est plus nécessaire, puisque l'UI est logique, même sans cet élément. L'interface est ainsi épurée. - Vous pouvez également ajouter un widget
SizedBox(height: 10)
entreBigCard
etElevatedButton
. Cela permet de séparer un peu plus les deux widgets. Le widgetSizedBox
prend de la place sans rien afficher directement. Il est communément utilisé pour créer des séparations visuelles.
Avec les modifications facultatives, MyHomePage
comporte le code suivant :
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
L'application doit ressembler à ceci :
Dans la section suivante, vous allez ajouter une fonctionnalité permettant de marquer comme favori (ou d'aimer) un mot généré.
6. Ajouter une fonctionnalité
L'application fonctionne et propose même des paires de mots intéressantes. Mais dès que l'utilisateur clique sur Next (Suivant), la paire de mots disparaît à jamais. Il peut être utile de trouver un moyen de mémoriser les meilleures propositions, comme un bouton "Like" (J'aime).
Ajouter la logique métier
Faites défiler jusqu'à MyAppState
et ajoutez le code suivant :
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
Examinez les modifications :
- Vous avez ajouté une propriété appelée
favorites
àMyAppState
. Cette propriété est initialisée avec une liste vide :[]
. - Vous avez également spécifié que la liste ne pouvait contenir que des paires de mots (
<WordPair>[]
) avec éléments génériques. Cela permet de consolider votre application : Dart refuse même de l'exécuter si vous tentez d'ajouter autre chose qu'uneWordPair
. Par conséquent, vous pouvez utiliser la listefavorites
en sachant qu'elle ne comportera jamais d'objets indésirables (commenull
) dissimulés.
- Vous avez aussi ajouté une méthode
toggleFavorite()
. Elle permet d'ajouter ou de supprimer l'actuelle paire de mots dans la liste des favoris (selon qu'elle s'y trouve ou non). Dans les deux cas de figure, le code appellenotifyListeners();
par la suite.
Ajouter le bouton
La logique métier étant en place, vous allez maintenant améliorer l'interface utilisateur. Pour placer un bouton "Like" (J'aime) à gauche du bouton "Next" (Suivant), vous devez utiliser une Row
. Le widget Row
est l'équivalent horizontal de la Column
que nous avons étudié précédemment.
Tout d'abord, encapsulez le bouton existant dans une Row
. Accédez à la méthode build()
de MyHomePage
, placez votre curseur sur l'ElevatedButton
, appelez le menu Refactor (Refactorisation) avec Ctrl+.
ou Cmd+.
, et sélectionnez Wrap with Row (Encapsuler avec une ligne).
Après avoir enregistré, notez que la Row
se comporte comme une Column
(par défaut, elle regroupe ses enfants à gauche). (La Column
regroupe ses enfants en haut.) Pour résoudre ce problème, vous pouvez de nouveau appliquer l'approche précédente avec mainAxisAlignment
. Cependant, vous allez utiliser mainAxisSize
à des fins pédagogiques (d'apprentissage). Cela indique à la Row
qu'elle ne doit pas occuper tout l'espace horizontal disponible.
Apportez la modification suivante :
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
L'UI est revenue à l'état précédent.
Ajoutez ensuite le bouton Like (J'aime) et associez-le à toggleFavorite()
. Pour pimenter un peu l'opération, essayez de procéder seul, sans regarder le bloc de code ci-dessous.
Tout va bien, même si vous n'avez pas procédé exactement comme indiqué ci-dessous. Vous n'avez pas à vous soucier de l'icône en forme de cœur, sauf si vous souhaitez vous confronter à un défi de taille.
Ce n'est absolument pas grave si vous n'y arrivez pas : c'est votre première heure avec Flutter après tout.
Voici un moyen d'ajouter le second bouton à MyHomePage
. Cette fois-ci, utilisez le constructeur ElevatedButton.icon()
pour créer un bouton avec une icône. En haut de la méthode build
, choisissez l'icône qui convient (selon que l'actuelle paire de mots se trouve déjà dans les favoris ou non). Notez aussi que SizedBox
est de nouveau utilisé pour séparer légèrement les deux boutons.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
L'application doit ressembler à ceci :
Malheureusement, l'utilisateur ne peut pas afficher les favoris. Le moment est venu d'ajouter un écran entièrement distinct à l'application. Retrouvons-nous dans la prochaine section !
7. Ajouter un rail de navigation
La plupart des applications ne rentrent pas dans un seul écran. Même si ce peut être le cas de notre application, nous allons créer un écran distinct réservé aux favoris de l'utilisateur à des fins pédagogiques. Pour basculer d'un écran à l'autre, vous allez implémenter votre premier StatefulWidget
.
Pour entrer dans le vif du sujet le plus rapidement possible, divisez MyHomePage
en deux widgets.
Sélectionnez l'intégralité de MyHomePage
, supprimez-la et remplacez-la par le code suivant :
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
Après avoir enregistré, notez que l'aspect visuel de l'UI est prêt, mais qu'elle ne fonctionne pas. Cliquer sur le cœur (♥︎) dans le rail de navigation n'a aucun effet.
Examinez les modifications.
- Tout d'abord, notez que l'intégralité du contenu de
MyHomePage
est extrait dans un nouveau widget,GeneratorPage
.Scaffold
constitue la seule partie de l'ancien widgetMyHomePage
à ne pas être extraite. - Le nouveau
MyHomePage
comporte unRow
avec deux enfants. Le premier widget estSafeArea
. Le second widget estExpanded
. - Le
SafeArea
garantit que l'enfant n'est pas masqué par une encoche matérielle ni une barre d'état. Dans cette application, le widget s'encapsule autour deNavigationRail
pour empêcher les boutons de navigation d'être masqués par une barre d'état mobile, par exemple. - Vous pouvez passer la ligne
extended: false
de NavigationRail surtrue
. Cela permet d'afficher les libellés en regard des icônes. Vous allez apprendre à exécuter automatiquement cette action lorsque l'application dispose d'un espace horizontal suffisant par la suite. - Le rail de navigation a deux destinations [Home (Accueil) et Favorites (Favoris)] avec icônes et libellés. Il définit également l'actuel
selectedIndex
. Un index sélectionné de "zéro" sélectionne la première destination. Un index sélectionné de "un" sélectionne la deuxième destination, et ainsi de suite. Pour le moment, il est codé en dur sur zéro. - Le rail de navigation définit également ce qui se passe lorsque l'utilisateur sélectionne l'une des destinations avec
onDestinationSelected
. Pour le moment, l'application génère simplement la valeur d'index requise avecprint()
. - Le widget
Expanded
est le deuxième enfant deRow
. Les widgets "Expanded" sont particulièrement utiles dans les lignes et les colonnes. Ils permettent d'exprimer la mise en page lorsque certains enfants n'utilisent que l'espace dont ils ont besoin (NavigationRail
, dans ce cas) et que les autres widgets doivent occuper le plus d'espace restant possible (Expanded
, dans ce cas). Vous pouvez considérer les widgetsExpanded
comme "voraces". Pour mieux apprécier le rôle de ces widgets, essayez d'encapsuler le widgetNavigationRail
avec un autreExpanded
. La mise en page obtenue ressemble à ceci :
- Deux widgets
Expanded
se partagent l'espace horizontal disponible, même si le rail de navigation n'a réellement besoin que d'un petit tronçon à gauche. - Le widget
Expanded
comporte unContainer
coloré. Ce conteneur comporte unGeneratorPage
.
Widgets avec état et widgets sans état
Jusqu'à présent, MyAppState
a répondu à tous vos besoins en termes d'état. C'est pourquoi l'ensemble des widgets que vous avez écrits jusqu'à présent étaient sans état. Il a ne comporte aucun état modifiable en soi. Aucun de ces widgets ne peut changer de lui-même. Ils doivent passer par MyAppState
.
La situation est sur le point d'évoluer.
Vous devez trouver un moyen de gérer la valeur du selectedIndex
du rail de navigation. Vous devez aussi pouvoir changer cette valeur via le rappel onDestinationSelected
.
Vous pourriez ajouter selectedIndex
comme une autre propriété de MyAppState
. Cela devrait fonctionner. Il est cependant facile d'imaginer que l'état de l'application deviendrait vite déraisonnable si chaque widget y conservait ses valeurs.
Certains états sont spécifiques à un widget et doivent donc rester avec lui.
Saisissez un StatefulWidget
, un type de widget avec un State
. Tout d'abord, convertissez MyHomePage
en un widget avec état.
Placez votre curseur sur la première ligne de MyHomePage
(qui commence par class MyHomePage...
) et appelez le menu Refactor (Refactorisation) avec Ctrl+.
ou Cmd+.
. Sélectionnez ensuite Convert to Stateful Widget (Convertir en widget avec état).
L'IDE crée une nouvelle classe pour vous, _MyHomePageState
. Cette classe étend State
et peut donc gérer ses propres valeurs. (Elle peut changer d'elle-même.) Notez aussi que la méthode build
de l'ancien widget sans état a été déplacée vers _MyHomePageState
(plutôt que de rester dans le widget). Elle a été déplacée textuellement (aucun élément de la méthode build
n'a été modifié). Elle réside désormais dans un autre endroit.
setState
Le nouveau widget avec état ne doit suivre qu'une seule variable, selectedIndex
. Apportez les trois modifications suivantes à _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
Examinez les modifications :
- Vous avez introduit une nouvelle variable
selectedIndex
et l'avez initialisée sur0
. - Vous avez utilisé cette nouvelle variable dans la définition de
NavigationRail
pour remplacer le0
codé en dur disponible jusqu'à présent. - Lors de l'appel du rappel
onDestinationSelected
, vous avez attribué la nouvelle valeur à unselectedIndex
dans un appelsetState()
plutôt que de l'imprimer dans la console. Cet appel est semblable à la méthodenotifyListeners()
précédemment utilisée et garantit la mise à jour de l'UI.
Le rail de navigation répond désormais aux interactions de l'utilisateur. Cependant, la zone étendue de droite reste la même. En effet, le code n'utilise pas selectedIndex
pour déterminer quel écran afficher.
Utiliser selectedIndex
Placez le code suivant en haut de la méthode build
de _MyHomePageState
, juste avant return Scaffold
:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
Examinez cet extrait de code :
- Le code déclare une nouvelle variable
page
de typeWidget
. - Une instruction "switch" attribue alors un écran à
page
en fonction de l'actuelle valeur deselectedIndex
. - Étant donné qu'il n'existe pas encore de
FavoritesPage
, utilisez le widgetPlaceholder
(très pratique) qui insère un rectangle barré là où vous le placez et indique que cette partie de l'UI n'est pas terminée.
- Conformément au principe d'échec accéléré, l'instruction "switch" veille à générer une erreur lorsque
selectedIndex
n'est ni 0 ni 1. Cela empêche les bugs après la ligne. Si vous avez déjà ajouté une destination au rail de navigation, mais que vous avez oublié de mettre à jour ce code, le programme plante pendant le développement (il vous évite d'avoir à deviner pourquoi l'application ne fonctionne pas et de publier des bugs dans le code en production).
Maintenant que page
comporte le widget à afficher sur la droite, vous avez sans doute deviné quelle autre modification est nécessaire.
Voici à quoi ressemble _MyHomePageState
après cette dernière modification :
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
L'application passe désormais du GeneratorPage
à l'espace réservé qui va bientôt devenir la page Favorites (Favoris).
Responsivité
Rendons maintenant le rail de navigation responsif : Il doit automatiquement afficher les libellés (avec extended: true
) lorsqu'il y a suffisamment d'espace.
Flutter fournit plusieurs widgets qui permettent de rendre vos applications automatiquement responsives. Par exemple, Wrap
est un widget semblable à Row
ou Column
qui encapsule automatiquement les enfants dans la ligne suivante (appelée "run") lorsque l'espace vertical ou horizontal est insuffisant. FittedBox
est un widget qui adapte automatiquement l'enfant à l'espace disponible, en fonction de vos spécifications.
Cependant, NavigationRail
n'affiche pas automatiquement les libellés lorsque l'espace est suffisant (il ne sait pas toujours à quoi correspond l'espace suffisant). Il appartient aux développeurs de définir cet appel.
Imaginons que vous décidiez d'afficher les libellés uniquement si la largeur de MyHomePage
est d'au moins 600 pixels.
Dans ce cas, le widget à utiliser est LayoutBuilder
. Il permet de modifier l'arborescence de widgets en fonction de l'espace disponible.
Là encore, utilisez le menu Refactor (Refactorisation) de Flutter (dans VS Code) pour apporter les modifications requises. Cette fois, l'opération est légèrement plus complexe :
- Dans la méthode
build
de_MyHomePageState
, placez votre curseur surScaffold
. - Appelez le menu Refactor (Refactorisation) avec
Ctrl+.
(Windows/Linux) ouCmd+.
(Mac). - Sélectionnez Wrap with Builder (Encapsuler avec un compilateur) et appuyez sur Enter (Entrée).
- Modifiez le nom du
Builder
récemment ajouté àLayoutBuilder
. - Basculez la liste des paramètres de rappel de
(context)
sur(context, constraints)
.
Le rappel builder
de LayoutBuilder
est appelé à chaque fois que les contraintes changent. Il peut s'agir des cas suivants :
- L'utilisateur redimensionne la fenêtre de l'application.
- L'utilisateur bascule son téléphone du mode portrait en mode paysage, et inversement.
- La taille d'un widget en regard de
MyHomePage
augmente, ce qui réduit les contraintes deMyHomePage
. - Et ainsi de suite.
Le code peut maintenant déterminer s'il faut afficher les libellés en interrogeant les actuelles constraints
. Apportez la modification suivante sur une seule ligne à la méthode build
de _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
Votre application répond désormais à son environnement (taille de l'écran, orientation et plate-forme, par exemple). En d'autres termes, elle est responsive.
il ne vous reste plus qu'à remplacer le Placeholder
par un véritable écran Favorites (Favoris). Ce sujet est abordé dans la section suivante.
8. Ajouter une page
Vous rappelez-vous le widget Placeholder
que nous avons utilisé pour remplacer la page Favorites (Favoris) ?
Nous allons nous y atteler.
Si vous êtes d'humeur audacieuse, essayez de procéder par vous-même. L'objectif est d'afficher une liste de favorites
dans un nouveau widget sans état (FavoritesPage
) et de remplacer le Placeholder
par ce widget.
Voici quelques conseils :
- Pour qu'une
Column
soit défilante, utilisez le widgetListView
. - Pour rappel, accédez à l'instance
MyAppState
depuis n'importe quel widget aveccontext.watch<MyAppState>()
. - Si vous souhaitez essayer de nouveaux widgets,
ListTile
comporte des propriétés commetitle
(généralement pour du texte),leading
(pour les icônes ou les avatars) ouonTap
(pour les interactions). Cependant, vous pouvez obtenir des résultats semblables avec les widgets que vous connaissez déjà. - Dart permet et d'utiliser les boucles
for
à l'intérieur des littéraux de collection. Par exemple, simessages
comporte une liste de chaînes, le code peut ressembler à ceci :
D'autre part, si vous maîtrisez davantage la programmation fonctionnelle, Dart vous permet aussi d'écrire du code comme messages.map((m) => Text(m)).toList()
. Vous pouvez bien sûr créer une liste de widgets et la compléter de façon impérative dans la méthode build
.
Ajouter vous-même la page Favorites (Favoris) vous permet d'en apprendre davantage en faisant vos propres choix. En revanche, vous pouvez rencontrer des problèmes que vous ne savez pas encore résoudre par vous-même. N'oubliez pas : tout va bien, même si vous n'y arrivez pas, cela constitue l'un des piliers de l'apprentissage. Personne ne peut vous demander de maîtriser le développement Flutter dès la première heure, pas même vous.
vous trouverez ci-dessous l'un des nombreux moyens pour implémenter la page des favoris. Nous espérons que cette implémentation vous incitera à jouer avec le code, à améliorer l'UI et à vous l'approprier.
Voici la nouvelle classe FavoritesPage
:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
Voilà ce que fait le widget :
- Il identifie l'état actuel de l'application.
- Lorsque la liste des favoris est vide, il affiche un message au centre : No favorites yet (Aucun favori pour le moment).
- Dans le cas contraire, il affiche une liste (défilante).
- La liste commence par un résumé [par exemple, You have 5 favorites (Vous avez cinq favoris)].
- Le code effectue ensuite une itération dans tous les favoris et crée un widget
ListTile
pour chacun d'entre eux.
Il ne vous reste plus qu'à remplacer le widget Placeholder
par un FavoritesPage
. Et voilà !
Vous pouvez récupérer le code final de cette application dans le dépôt de l'atelier de programmation sur GitHub.
9. Étapes suivantes
Félicitations !
Bravo ! Vous avez transformé une structure non fonctionnelle avec un widget Column
et deux widgets Text
en une ravissante petite application responsive.
Points abordés
- Fonctionnement de base de Flutter
- Créer des mises en page sous Flutter
- Associer les interactions utilisateur (appuis de bouton, par exemple) aux comportements de l'application
- Assurer l'organisation du code Flutter
- Rendre l'application responsive
- Proposer une interface homogène
Quelles sont les prochaines étapes ?
- Approfondissez votre expérience avec l'application que vous avez développée dans cet atelier.
- Étudiez la version avancée du code de cette application pour découvrir comment ajouter des listes animées, des dégradés, des fondus enchaînés et bien plus encore.
- Suivez votre évolution sur flutter.dev/learn.