Créer des interfaces utilisateur esthétiques avec Flutter

Flutter est un kit d'interface utilisateur Google qui permet de développer des applications esthétiques compilées de manière native pour les mobiles, le Web et les ordinateurs de bureau, à partir d'un seul codebase. Dans cet atelier de programmation, vous allez créer une application de chat simple pour Android, iOS et (éventuellement) le Web.

Cet atelier offre une approche plus approfondie de Flutter que ne le font les partie 1 et partie 2 des ateliers de programmation expliquant comment développer votre première application Flutter. Pour une introduction à Flutter moins exhaustive, commencez par ces deux ateliers.

Ce que vous allez apprendre

  • Développer une application Flutter qui s'intègre naturellement dans Android et iOS
  • Utiliser l'IDE Android Studio, avec de nombreux raccourcis pris en charge par le plug-in Flutter pour Android Studio et IntelliJ
  • Déboguer votre application Flutter
  • Exécuter votre application Flutter sur un émulateur, un simulateur et un appareil

Qu'attendez-vous de cet atelier de programmation ?

Je suis novice en la matière et je voudrais avoir un bon aperçu. Je connais un peu le sujet, mais j'aimerais revoir certains points. Je recherche un exemple de code à utiliser dans mon projet. Je cherche des explications sur un point spécifique.

Cet atelier de programmation nécessite deux logiciels : le SDK Flutter (téléchargeable ici) et un éditeur (à configurer). Il suppose que vous utilisiez Android Studio. En revanche, vous pouvez utiliser l'éditeur de votre choix.

Vous pouvez exécuter cet atelier de programmation sur l'un des appareils suivants :

  • Un appareil physique (Android ou iOS) connecté à votre ordinateur et réglé en mode développeur
  • L'outil Android Emulator
  • Le simulateur iOS
  • Le navigateur Google Chrome
  • Le bureau Windows, macOS ou Linux (si vous activez la prise en charge des ordinateurs de bureau pour Flutter)

Si vous utilisez Android, vous devez effectuer une configuration dans Android Studio. Si vous utilisez iOS, Xcode doit également être installé sur votre ordinateur Mac. Pour en savoir plus, consultez la section Configurer un éditeur.

Créez une application Flutter simple à partir d'un modèle. Vous allez modifier cette application de départ pour créer l'application finale.

b2f84ff91b0e1396.pngLancez Android Studio.

  1. Si vous n'avez pas de projets ouverts, sélectionnez Start a new Flutter app (Démarrer une nouvelle application Flutter) sur la page d'accueil. Sinon, sélectionnez File > New > New Flutter Project (Fichier > Nouveau > Nouveau projet Flutter).
  2. Sélectionnez Flutter Application (Application Flutter) comme type de projet, puis cliquez sur Next (Suivant).
  3. Vérifiez que le chemin d'accès au SDK Flutter spécifie l'emplacement du SDK. Sélectionnez Install SDK (Installer le SDK) si le champ de texte est vide.
  4. Saisissez friendly_chat en nom de projet, puis cliquez sur Next (Suivant).
  5. Utilisez le nom de package par défaut suggéré par Android Studio, puis cliquez sur Next (Suivant).
  6. Cliquez sur Finish (Terminer).
  7. Attendez qu'Android Studio installe le SDK et crée le projet.

b2f84ff91b0e1396.pngVous pouvez également créer une application Flutter sur la ligne de commande.

$ flutter create friendly_chat
$ cd friendly_chat
$ dart migrate --apply-changes
$ flutter run

Des problèmes ?

Pour en savoir plus sur la création d'une application simple basée sur un modèle, consultez la page Test drive (Version test). Vous pouvez également utiliser le code disponible via l'un des liens ci-dessous pour résoudre le problème.

Dans cette section, vous allez commencer à modifier l'application exemple par défaut pour en faire une application de chat. L'objectif est d'utiliser Flutter pour créer FriendlyChat, une application de chat simple et extensible présentant les caractéristiques suivantes :

  • L'application affiche les messages en temps réel.
  • Les utilisateurs peuvent saisir un message, puis l'envoyer en appuyant sur la touche Entrée ou sur le bouton Envoyer.
  • L'interface utilisateur s'exécute sur les appareils Android et iOS, ainsi que sur le Web.

Essayez l'application finale sur DartPad.

Créer l'échafaudage de l'application principale

Le premier élément que vous allez ajouter est une barre d'application simple qui affiche le titre de l'application de manière fixe. À mesure que vous progressez dans les sections suivantes de cet atelier de programmation, vous ajouterez progressivement à l'application d'autres éléments d'interface utilisateur réactifs et avec état.

Le fichier main.dart se trouve dans le répertoire lib de votre projet Flutter. Il contient la fonction main() qui lance l'exécution de votre application.

b2f84ff91b0e1396.pngRemplacez tout le code du fichier main.dart par le code suivant :

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'FriendlyChat',
      home: Scaffold(
        appBar: AppBar(
          title: Text('FriendlyChat'),
        ),
      ),
    ),
  );
}

cf1e10b838bf60ee.png Observations

  • Tout programme Dart, qu'il s'agisse d'une application de ligne de commande, AngularDart ou Flutter, commence par une fonction main().
  • Les définitions des fonctions main() et runApp() sont identiques à celles de l'application générée automatiquement.
  • La fonction runApp() utilise un Widget comme argument, que le framework Flutter développe et affiche à l'écran au moment de l'exécution.
  • Cette application de chat utilise des éléments Material Design dans l'interface utilisateur. Un objet MaterialApp est ainsi créé et transmis à la fonction runApp(). Le widget MaterialApp devient la racine de l'arborescence des widgets de votre application.
  • L'argument home spécifie l'écran par défaut que les utilisateurs voient dans votre application. Dans ce cas, il s'agit d'un widget Scaffold qui possède une AppBar simple comme widget enfant. C'est typique pour une application Material.

b2f84ff91b0e1396.pngExécutez l'application en cliquant sur l'icône Exécuter 6869d41b089cc745.png dans l'éditeur. Lorsque vous exécutez une application pour la première fois, l'opération peut prendre un certain temps. L'application est plus rapide aux étapes suivantes.

febbb7a3b70462b7.png

Vous devriez obtenir un écran semblable à celui-ci :

Pixel 3XL

iPhone 11

Créer l'écran de chat

Pour jeter les bases des composants interactifs, vous devez diviser l'application simple en deux sous-classes différentes de widget : un widget FriendlyChatApp à la racine qui ne change jamais, et un widget enfant ChatScreen qui se recompile lorsque des messages sont envoyés et que l'état interne change. Pour l'instant, ces deux classes peuvent étendre StatelessWidget. Par la suite, vous modifierez ChatScreen pour en faire un widget avec état. De cette façon, vous pouvez modifier son état selon vos besoins.

b2f84ff91b0e1396.pngCréez le widget FriendlyChatApp :

  1. Dans main(), placez le curseur devant le M de MaterialApp.
  2. Effectuez un clic droit et sélectionnez Refactor > Extract > Extract Flutter widget (Refactoriser > Extraire > Extraire le widget Flutter).

a133a9648f86738.png

  1. Saisissez FriendlyChatApp dans la boîte de dialogue ExtractWidget, puis cliquez sur le bouton Refactor (Refactoriser). Le code MaterialApp est placé dans un nouveau widget sans état intitulé FriendlyChatApp, et main() est mis à jour pour appeler cette classe lorsqu'elle appelle la fonction runApp().
  2. Sélectionnez le bloc de texte après home:. Commencez à partir de Scaffold( jusqu'à sa parenthèse fermante, ). N'incluez pas la virgule de fin.
  3. Commencez à saisir ChatScreen, et sélectionnez ChatScreen() dans le pop-up. (Sélectionnez l'entrée ChatScreen marquée par un signe égal dans le cercle jaune. Vous obtiendrez ainsi une classe avec des parenthèses vides au lieu d'une constante.)

b2f84ff91b0e1396.pngCréez un widget sans état, ChatScreen :

  1. Dans la classe FriendlyChatApp, vers la ligne 27, commencez à saisir stless. L'éditeur vous demande si vous voulez créer un widget Stateless. Appuyez sur Entrée pour accepter. Le code récurrent s'affiche et le curseur est positionné de sorte que vous puissiez saisir le nom du widget sans état.
  2. Saisissez ChatScreen.

b2f84ff91b0e1396.pngModifiez le widget ChatScreen :

  1. Dans le widget ChatScreen, sélectionnez Container (Conteneur), puis commencez à saisir Scaffold. Sélectionnez Scaffold (Échafaudage) dans le pop-up.
  2. Le curseur devrait être placé entre les parenthèses. Appuyez sur Entrée pour commencer une nouvelle ligne.
  3. Commencez à saisir appBar, et sélectionnez appBar: (Barre d'application) dans le pop-up.
  4. Après appBar:, commencez à saisir AppBar, et sélectionnez la classe AppBar dans le pop-up.
  5. Entre les parenthèses, commencez à saisir title, et sélectionnez title: (titre) dans le pop-up.
  6. Après title:, commencez à saisir Text, et sélectionnez la classe Text (Texte).
  7. Le code récurrent pour Text contient le mot data. Supprimez la première virgule après data. Sélectionnez le mot data, et remplacez-le par 'FriendlyChat'. (Dart accepte les guillemets simples ou doubles, mais préfère les guillemets simples, sauf si le texte en contient déjà).

Regardez en haut à droite du volet de code. Si une coche verte est affichée, l'analyse de votre code est concluante. Félicitations !

cf1e10b838bf60ee.png Observations

Cette étape présente plusieurs concepts clés du framework Flutter :

  • Vous décrivez la partie de l'interface utilisateur représentée par un widget dans sa méthode build(). Le framework appelle les méthodes build() pour FriendlyChatApp et ChatScreen lors de l'insertion de ces widgets dans l'arborescence de widgets, et lorsque leurs dépendances changent.
  • @override est une annotation Dart qui indique que la méthode balisée remplace la méthode d'une super-classe.
  • Certains widgets, comme Scaffold et AppBar, sont spécifiques aux applications Material Design. D'autres, comme Text, sont génériques et peuvent être utilisés dans n'importe quelle application. Les widgets de différentes bibliothèques du framework Flutter sont compatibles et peuvent fonctionner ensemble dans une seule application.
  • La simplification de la méthode main() permet l'actualisation à chaud, car celle-ci ne réexécute pas main().

b2f84ff91b0e1396.pngCliquez sur le bouton d'actualisation à chaud 48583acd5d1a5e12.png pour voir les modifications presque instantanément. Une fois l'interface utilisateur divisée en classes distinctes et le widget racine modifié, vous ne devriez noter aucun changement visible dans l'interface utilisateur.

Des problèmes ?

Si votre application ne s'exécute pas correctement, vérifiez que vous n'avez pas fait de faute de frappe. Si nécessaire, utilisez le code disponible via le lien ci-dessous pour résoudre le problème.

Dans cette section, vous allez apprendre à créer une commande permettant à l'utilisateur de saisir et d'envoyer des messages de chat.

64fd9c97437a7461.png

Sur un appareil, si vous cliquez sur le champ de texte, un clavier virtuel s'affiche. Les utilisateurs peuvent envoyer des messages de chat en saisissant une chaîne non vide et en appuyant sur la touche Entrée du clavier virtuel. Ils peuvent également envoyer leurs messages à l'utilisateur en appuyant sur le bouton Envoyer situé à côté du champ de saisie.

Pour le moment, l'interface utilisateur permettant de rédiger des messages se trouve en haut de l'écran de chat. Cependant, une fois que vous aurez ajouté l'interface utilisateur permettant d'afficher les messages à l'étape suivante, vous pourrez la déplacer en bas de l'écran de chat.

Ajouter un champ de saisie interactif

Le framework Flutter fournit un widget Material Design intitulé TextField. Il s'agit d'un StatefulWidget (un widget dont l'état peut changer) avec des propriétés pour personnaliser le comportement du champ de saisie. State est une information qui peut être lue de manière synchrone lors de la compilation du widget et qui peut changer pendant sa durée de vie. L'ajout du premier widget avec état à l'application Friendly Chat nécessite quelques modifications.

b2f84ff91b0e1396.pngModifiez la classe ChatScreen de sorte qu'elle soit avec état :

  1. Sélectionnez ChatScreen sur la ligne class ChatScreen extends StatelessWidget.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu.
  3. Dans le menu, sélectionnez Convert to StatefulWidget (Convertir en StatefulWidget). La classe est automatiquement modifiée avec le code récurrent d'un widget avec état, y compris une nouvelle classe _ChatScreenState pour gérer l'état.

Pour gérer les interactions avec le champ de texte, vous devez utiliser un objet TextEditingController pour lire le contenu du champ de saisie et pour effacer le champ après l'envoi du message de chat.

b2f84ff91b0e1396.pngAjoutez un TextEditingController à _ChatScreenState.

Ajoutez ce qui suit à la première ligne de la classe _ChatScreenState :

final _textController = TextEditingController();

Maintenant que votre application peut gérer l'état, vous pouvez créer la classe _ChatScreenState avec un champ de saisie et un bouton Envoyer.

b2f84ff91b0e1396.pngAjoutez une fonction _buildTextComposer à _ChatScreenState :

  Widget _buildTextComposer() {
    return  Container(
        margin: EdgeInsets.symmetric(horizontal: 8.0),
      child: TextField(
        controller: _textController,
        onSubmitted: _handleSubmitted,
        decoration: InputDecoration.collapsed(
            hintText: 'Send a message'),
      ),
    );
  }

cf1e10b838bf60ee.png Observations

  • Dans Flutter, les données avec état d'un widget sont encapsulées dans un objet State. L'objet State est ensuite associé à un widget qui étend la classe StatefulWidget.
  • Le code ci-dessus définit une méthode privée intitulée _buildTextComposer() qui renvoie un widget Container avec un widget TextField configuré.
  • Le widget Container ajoute une marge horizontale entre le bord de l'écran et chaque côté du champ de saisie.
  • Les unités transmises à EdgeInsets.symmetric sont des pixels logiques qui sont convertis en un nombre spécifique de pixels physiques, en fonction du rapport de pixels de l'appareil. Vous connaissez peut-être le terme équivalent pour Android (pixels indépendants de la densité) ou pour iOS (points).
  • La propriété onSubmitted fournit une méthode de rappel privée, _handleSubmitted(). Dans un premier temps, cette méthode efface simplement le champ, mais vous pourrez l'étendre ultérieurement pour envoyer le message de chat.
  • Le TextField avec le TextEditingController vous permet de contrôler le champ de texte. Ce contrôleur efface ce champ et lit la valeur correspondante.

b2f84ff91b0e1396.pngAjoutez la fonction _handleSubmitted à _ChatScreenState pour effacer le contrôleur de texte :

  void _handleSubmitted(String text) {
    _textController.clear();
  }

Ajouter un widget de rédaction de texte

b2f84ff91b0e1396.pngModifiez la méthode build() pour _ChatScreenState.

Après la ligne appBar: AppBar(...), ajoutez une propriété body: :

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FriendlyChat')),
      body: _buildTextComposer(),    // NEW
    );
  }

cf1e10b838bf60ee.png Observations

  • La méthode _buildTextComposer renvoie un widget qui encapsule le champ de saisie.
  • Si vous ajoutez _buildTextComposer à la propriété body, l'application affiche la commande utilisateur de saisie du texte.

b2f84ff91b0e1396.pngEffectuez une actualisaton à chaud de l'application. Vous devriez obtenir un écran semblable à celui-ci :

Pixel 3XL

iPhone 11

Ajouter un bouton d'envoi réactif

Ajoutez ensuite un bouton Envoyer à droite du champ de texte. Pour cela, vous devez structurer davantage la mise en page.

b2f84ff91b0e1396.pngDans la fonction _buildTextComposer, encapsulez le TextField dans une Row:

  1. Sélectionnez TextField dans _buildTextComposer.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu, puis sélectionnez Wrap with widget (Encapsuler avec un widget). Un nouveau widget qui encapsule le TextField est ajouté. Le nom de l'espace réservé est sélectionné, et l'IDE attend que vous indiquiez un nouveau nom d'espace réservé.
  3. Commencez à saisir Row, et sélectionnez Row (Ligne) dans la liste qui s'affiche. Un pop-up contenant la définition du constructeur de Row s'ouvre. La propriété child comporte une bordure rouge, et l'analyseur indique qu'il vous manque la propriété children requise.
  4. Pointez sur child. Dans le pop-up qui s'affiche, il vous est demandé si vous voulez remplacer la propriété par children. Sélectionnez cette option.
  5. La propriété children utilise une liste plutôt qu'un seul widget. (Pour le moment, la liste ne contient qu'un seul élément, mais vous allez bientôt en ajouter un autre.) Convertissez le widget en une liste d'un élément en saisissant un crochet ouvrant ([) après le texte children:. L'éditeur fournit également la parenthèse fermante de droite. Supprimez le crochet fermant. Quelques lignes plus bas, juste avant la parenthèse droite qui ferme la ligne, saisissez le crochet fermant, suivi d'une virgule (],). L'analyseur devrait maintenant afficher une coche verte.
  6. Le code est désormais correct, mais n'est pas bien formaté. Effectuez un clic droit dans le volet du code, puis sélectionnez Reformat Code with dartfmt (Reformater le code avec dartfmt).

b2f84ff91b0e1396.pngEncapsulez le TextField dans un Flexible :

  1. Sélectionnez Row.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu, puis sélectionnez Wrap with widget (Encapsuler avec un widget). Un nouveau widget qui encapsule le TextField est ajouté. Le nom de l'espace réservé est sélectionné, et l'IDE attend que vous indiquiez un nouveau nom d'espace réservé.
  3. Commencez à saisir Flexible, et sélectionnez Flexible dans la liste qui s'affiche. Un pop-up contenant la définition du constructeur de Row s'ouvre.
Widget _buildTextComposer() {
  return  Container(
    margin: EdgeInsets.symmetric(horizontal: 8.0),
    child:  Row(                             // NEW
      children: [                            // NEW
         Flexible(                           // NEW
          child:  TextField(
            controller: _textController,
            onSubmitted: _handleSubmitted,
            decoration:  InputDecoration.collapsed(
                hintText: 'Send a message'),
          ),
        ),                                    // NEW
      ],                                      // NEW
    ),                                        // NEW
  );
}

cf1e10b838bf60ee.png Observations

  • L'utilisation d'une Row vous permet de placer le bouton Send (Envoyer) à côté du champ de saisie.
  • Le fait d'encapsuler le TextField dans un widget Flexible indique à la Row d'ajuster automatiquement la taille du champ de texte pour utiliser l'espace restant non utilisé par le bouton.
  • L'ajout d'une virgule après un crochet fermant indique au formateur comment formater le code.

Vous allez ajouter ensuite un bouton Send (Envoyer). Comme il s'agit d'une application Material, utilisez l'icône Material 2de111ba4b057a1e.png correspondante :

b2f84ff91b0e1396.pngAjoutez le bouton Send (Envoyer) à la Row.

Le bouton Send (Envoyer) devient le deuxième élément de la liste Row.

  1. Placez le curseur à la fin du crochet fermant et de la virgule du widget Flexible, puis appuyez sur Entrée pour commencer une nouvelle ligne.
  2. Commencez à saisir Container, et sélectionnez Container (Conteneur) dans le pop-up. Le curseur est placé entre les parenthèses du conteneur. Appuyez sur Entrée pour commencer une nouvelle ligne.
  3. Ajoutez les lignes de code suivantes au conteneur :
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
    icon: const Icon(Icons.send),
    onPressed: () => _handleSubmitted(_textController.text)),

cf1e10b838bf60ee.png Observations

  • L'IconButton affiche le bouton Send (Envoyer).
  • La propriété icon spécifie la constante Icons.send de la bibliothèque Material pour créer une instance Icon.
  • En plaçant l'IconButton à l'intérieur d'un widget Container, vous pouvez personnaliser l'espacement des marges du bouton de sorte qu'il soit visuellement mieux positionné à côté de votre champ de saisie.
  • La propriété onPressed utilise une fonction anonyme pour appeler la méthode _handleSubmitted() et transmet le contenu du message à l'aide de _textController.
  • Dans Dart, la syntaxe de flèche (=> expression) est parfois utilisée pour déclarer des fonctions. Ce raccourci pour { return expression; } n'est utilisé que pour les fonctions sur une ligne. Pour avoir un aperçu de la prise en charge des fonctions Dart, y compris des fonctions anonymes et imbriquées, consultez la Présentation du langage Dart.

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de l'application pour voir le bouton Send (Envoyer) :

Pixel 3XL

iPhone 11

La couleur noire du bouton vient du thème Material Design par défaut. Pour attribuer une couleur d'accentuation aux icônes de votre application, transmettez l'argument de couleur à IconButton ou appliquez un autre thème.

b2f84ff91b0e1396.pngDans _buildTextComposer(), encapsulez le Container dans un IconTheme:.

  1. Sélectionnez Container en haut de la fonction _buildTextComposer().
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu, puis sélectionnez Wrap with widget (Encapsuler avec un widget). Un nouveau widget qui encapsule le Container est ajouté. Le nom de l'espace réservé est sélectionné, et l'IDE attend que vous indiquiez un nouveau nom d'espace réservé.
  3. Commencez à saisir IconTheme, et sélectionnez IconTheme dans la liste. La propriété child est entourée par un cadre rouge, et l'analyseur vous indique que la propriété data est obligatoire.
  4. Ajoutez la propriété data :
return IconTheme(
  data: IconThemeData(color: Theme.of(context).accentColor), // NEW
  child: Container(

cf1e10b838bf60ee.png Observations

  • La couleur, l'opacité et la taille des icônes sont issues d'un widget IconTheme qui utilise un objet IconThemeData pour définir ces caractéristiques.
  • La propriété data du widget IconTheme spécifie l'objet ThemeData pour le thème actuel. La couleur d'accentuation du thème actuel est ainsi attribuée au bouton (et à toute autre icône de cette partie de l'arborescence des widgets).
  • Un objet BuildContext est un handle pour l'emplacement d'un widget dans l'arborescence de widgets de votre application. Chaque widget possède son propre objet BuildContext, lequel devient le parent du widget renvoyé par la fonction StatelessWidget.build ou State.build. Autrement dit, _buildTextComposer() peut accéder à l'objet BuildContext à partir de son objet State d'encapsulation. Vous n'avez pas besoin de transmettre explicitement le contexte à la méthode.

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de l'application. Le bouton Send (Envoyer) devrait maintenant être bleu :

Pixel 3XL

iPhone 11

Des problèmes ?

Si votre application ne s'exécute pas correctement, vérifiez que vous n'avez pas fait de faute de frappe. Si nécessaire, utilisez le code disponible via le lien ci-dessous pour résoudre le problème.

e57d18c5bb8f2ac7.pngVous avez trouvé quelque chose de spécial !

Il y a plusieurs façons de déboguer votre application. Vous pouvez soit utiliser votre IDE directement pour définir des points d'arrêt, soit utiliser les outils pour les développeurs Dart (à ne pas confondre avec les outils pour les développeurs Chrome). Cet atelier de programmation montre comment définir des points d'arrêt avec Android Studio et IntelliJ. Si vous utilisez un autre éditeur, comme VS Code, procédez au débogage à l'aide des outils pour les développeurs. Pour en savoir plus sur les outils pour les développeurs Dart, consultez l'étape 2.5 de l'atelier Écrire votre première application Flutter sur le Web.

Les IDE Android Studio et IntelliJ vous permettent de déboguer des applications Flutter exécutées sur un émulateur, un simulateur ou un appareil. Ces éditeurs vous permettent d'effectuer ce qui suit :

  • Sélectionner un appareil ou un simulateur sur lequel déboguer votre application
  • Afficher les messages de la console
  • Définir des points d'arrêt dans votre code
  • Examiner les variables et évaluer les expressions lors de l'exécution

Les éditeurs Android Studio et IntelliJ affichent le journal système pendant l'exécution de votre application et fournissent une interface utilisateur Debugger pour fonctionner avec des points d'arrêt et contrôler le flux d'exécution.

6ea611ca007eb43c.png

Utiliser des points d'arrêt

b2f84ff91b0e1396.pngDéboguez votre application Flutter à l'aide de points d'arrêt :

  1. Ouvrez le fichier source dans lequel vous voulez définir un point d'arrêt.
  2. Recherchez la ligne où vous voulez définir un point d'arrêt, cliquez dessus, puis sélectionnez Run > Toggle Line Breakpoint (Exécuter > Activer/Désactiver un point d'arrêt sur la ligne). Vous pouvez également cliquer sur l'énoncé (à droite du numéro de ligne) pour activer/désactiver un point d'arrêt.
  3. Si vous n'étiez pas en mode débogage, arrêtez l'application.
  4. Redémarrez l'application en cliquant sur Run > Debug (Exécuter > Déboguer) ou sur le bouton Run debug (Exécuter le débogage) dans l'interface utilisateur.

L'éditeur lance l'interface utilisateur de Debugger et suspend l'exécution de votre application lorsque le point d'arrêt est atteint. Vous pouvez ensuite utiliser les commandes de l'interface utilisateur de Debugger pour identifier la cause de l'erreur.

Exercez-vous à utiliser le débogueur en définissant des points d'arrêt sur les méthodes build() dans l'application FriendlyChat, puis exécutez et déboguez l'application. Vous pouvez inspecter les blocs de pile pour afficher l'historique des appels de méthode de votre application.

Maintenant que l'écran et l'échafaudage de l'application sont en place, vous pouvez définir la zone d'affichage des messages de chat.

de23b9bb7bf84592.png

Implémenter une liste de messages de chat

Dans cette section, vous allez créer un widget qui affiche les messages de chat à l'aide de la composition (création et combinaison de plusieurs widgets plus petits). Vous commencerez par un widget représentant un seul message de chat. Vous l'imbriquerez ensuite dans une liste déroulante parent que vous imbriquerez à son tour dans l'échafaudage de l'application de base.

b2f84ff91b0e1396.pngAjoutez le widget sans état ChatMessage :

  1. Placez le curseur après la classe FriendlyChatApp et commencez à saisir stless. (L'ordre des classes n'a pas d'importance, mais il permet de comparer plus facilement votre code avec la solution.)
  2. Saisissez ChatMessage comme nom de classe.

b2f84ff91b0e1396.pngAjoutez une Row à la méthode build() pour ChatMessage :

  1. Placez le curseur entre les parenthèses de return Container() et appuyez sur Entrée pour commencer une nouvelle ligne.
  2. Ajoutez une propriété margin :
margin: EdgeInsets.symmetric(vertical: 10.0),
  1. L'enfant du Container' sera une Row. La liste de la Row contient deux widgets : un avatar et une colonne de texte.
return Container(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Container(
        margin: const EdgeInsets.only(right: 16.0),
        child: CircleAvatar(child: Text(_name[0])),
      ),
      Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(_name, style: Theme.of(context).textTheme.headline4),
          Container(
            margin: EdgeInsets.only(top: 5.0),
            child: Text(text),
          ),
        ],
      ),
    ],
  ),
);
  1. Ajoutez une variable text et un constructeur en haut de ChatMessage :
class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text}); // NEW
  final String text;                 // NEW

À ce stade, le seul problème que devrait relever l'analyseur serait que _name n'est pas défini. Vous allez le résoudre ci-après.

b2f84ff91b0e1396.pngDéfinissez la variable _name.

Définissez la variable _name comme indiqué, en remplaçant Your Name par votre propre nom. Cette variable permet de spécifier le nom de l'expéditeur pour chaque message de chat. Dans cet atelier de programmation, vous allez coder la valeur en dur pour plus de simplicité. Toutefois, la plupart des applications récupèrent le nom de l'expéditeur lors de l'authentification. Après la fonction main(), ajoutez la ligne suivante :

String _name = 'Your Name';

cf1e10b838bf60ee.png Observations

  • La méthode build() du ChatMessage renvoie une Row qui affiche un avatar graphique simple pour représenter l'utilisateur qui a envoyé le message de chat, un widget Column contenant le nom de l'expéditeur, ainsi que le texte du message.
  • Le CircleAvatar est personnalisé via l'ajout de la première initiale de l'utilisateur en transmettant le premier caractère de la valeur de la variable _name à un widget enfant Text.
  • Le paramètre crossAxisAlignment spécifie CrossAxisAlignment.start dans le constructeur Row pour positionner l'avatar et les messages par rapport à leurs widgets parents. Pour l'avatar, le parent est un widget Row dont l'axe principal est horizontal. CrossAxisAlignment.start lui attribue ainsi la position la plus élevée le long de l'axe vertical. Pour les messages, le parent est un widget Column dont l'axe principal est vertical. CrossAxisAlignment.start aligne le texte à la position la plus à gauche le long de l'axe horizontal.
  • À côté de l'avatar, deux widgets Text sont alignés verticalement pour afficher le nom de l'expéditeur en haut et le texte du message en dessous.
  • Theme.of(context) fournit l'objet Flutter ThemeData par défaut pour l'application. Plus tard, vous remplacerez ce thème par défaut afin de donner un style différent à votre application pour Android et iOS.
  • La propriété textTheme de ThemeData vous permet d'accéder à des styles logiques Material Design pour du texte tel que headline4. Cela vous évite de coder en dur les tailles de police et d'autres attributs de texte. Dans cet exemple, le nom de l'expéditeur est défini afin d'être plus grand que le texte du message.

b2f84ff91b0e1396.png Effectuez une actualisation à chaud de l'application.

Saisissez des messages dans le champ de texte. Appuyez sur le bouton Send (Envoyer) pour effacer le message. Saisissez un message long dans le champ de texte pour voir ce qui se passe lorsque le champ de texte déborde. Plus tard à l'étape 9, vous allez encapsuler la colonne dans un widget Expanded pour encapsuler le widget Text.

Implémenter une liste de messages de chat dans l'interface utilisateur

La prochaine amélioration consiste à récupérer la liste des messages de chat pour l'afficher dans l'interface utilisateur. Vous voulez que les utilisateurs puissent faire défiler cette liste afin de consulter l'historique des messages. Cette liste doit également présenter les messages dans l'ordre chronologique, avec le plus récent tout en bas de la liste visible.

b2f84ff91b0e1396.pngAjoutez une liste _messages à _ChatScreenState.

Dans la définition de _ChatScreenState, ajoutez un membre List intitulé _messages pour représenter chaque message de chat :

class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];      // NEW
  final _textController = TextEditingController();

b2f84ff91b0e1396.pngModifiez la méthode _handleSubmitted() dans _ChatScreenState.

Lorsque l'utilisateur envoie un message de chat à partir du champ de texte, l'application devrait ajouter le nouveau message à la liste des messages. Modifiez la méthode _handleSubmitted() pour implémenter ce comportement :

void _handleSubmitted(String text) {
  _textController.clear();
  ChatMessage message = ChatMessage(    //NEW
    text: text,                         //NEW
  );                                    //NEW
  setState(() {                         //NEW
    _messages.insert(0, message);       //NEW
  });                                   //NEW
 }

b2f84ff91b0e1396.pngRemettez le curseur dans le champ de texte après l'envoi du contenu.

  1. Ajoutez un FocusNode à _ChatScreenState :
class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();    // NEW
  1. Ajoutez la propriété focusNode au TextField dans _buildTextComposer() :
child: TextField(
  controller: _textController,
  onSubmitted: _handleSubmitted,
  decoration: InputDecoration.collapsed(hintText: 'Send a message'),
  focusNode: _focusNode,  // NEW
),
  1. Dans _handleSubmitted(), après l'appel à setState(), demandez que le curseur soit dans le TextField :
    setState(() {
      _messages.insert(0, message);
    });
    _focusNode.requestFocus();  // NEW

cf1e10b838bf60ee.png Observations

  • Chaque élément de la liste est une instance de ChatMessage.
  • La liste est initialisée pour être vide.
  • Lorsque vous appelez setState() pour modifier _messages, le framework sait que cette partie de l'arborescence de widgets a été modifiée et qu'il doit alors recompiler l'interface utilisateur. Seules les opérations synchrones doivent être effectuées dans setState(). Sinon, le framework pourrait recompiler les widgets avant la fin de l'opération.
  • En général, il est possible d'appeler setState() avec une fermeture vide après la modification de certaines données privées en dehors de cet appel de méthode. Pour ne pas oublier de l'appeler plus tard, nous vous recommandons toutefois de modifier les données à l'intérieur de la fermeture de setState.

b2f84ff91b0e1396.png Effectuez une actualisation à chaud de l'application.

Saisissez du texte dans le champ de texte, puis appuyez sur Return. Le champ de texte est de nouveau sélectionné.

Placer la liste des messages

Vous pouvez maintenant afficher la liste des messages de chat. Récupérez les widgets ChatMessage dans la liste _messages, puis placez-les dans un widget ListView afin d'obtenir une liste déroulante.

b2f84ff91b0e1396.pngDans la méthode build() pour _ChatScreenState, ajoutez une ListView à l'intérieur d'une Column :

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text ('FriendlyChat')),
    body: Column(                                            // MODIFIED
      children: [                                            // NEW
        Flexible(                                            // NEW
          child: ListView.builder(                           // NEW
            padding: EdgeInsets.all(8.0),                    // NEW
            reverse: true,                                   // NEW
            itemBuilder: (_, int index) => _messages[index], // NEW
            itemCount: _messages.length,                     // NEW
          ),                                                 // NEW
        ),                                                   // NEW
        Divider(height: 1.0),                                // NEW
        Container(                                           // NEW
          decoration: BoxDecoration(
            color: Theme.of(context).cardColor),             // NEW
          child: _buildTextComposer(),                       // MODIFIED
        ),                                                   // NEW
      ],                                                     // NEW
    ),                                                       // NEW
  );
}

cf1e10b838bf60ee.png Observations

  • La méthode d'usine ListView.builder crée une liste à la demande en fournissant une fonction qui est appelée une fois par élément de la liste. La fonction renvoie un nouveau widget à chaque appel. Le compilateur détecte également automatiquement les mutations de son paramètre children et lance une recompilation.
  • Les paramètres transmis au constructeur ListView.builder permettent de personnaliser le contenu et l'apparence de la liste :
  • padding crée un espace autour du texte du message.
  • itemCount spécifie le nombre de messages dans la liste.
  • itemBuilder fournit la fonction qui compile chaque widget dans [index]. Comme vous n'avez pas besoin du contexte de compilation actuel, vous pouvez ignorer le premier argument du IndexedWidgetBuilder. Lorsque l'argument est nommé avec un trait de soulignement (_) sans rien d'autre, cela signifie qu'il ne sera pas utilisé.
  • La propriété body du widget Scaffold contient désormais la liste des messages entrants, le champ de saisie et le bouton Envoyer. La mise en page utilise les widgets suivants :
  • Column : affiche ses enfants directs verticalement. Le widget Column utilise une liste de widgets enfants (identique à une Row) qui devient une liste déroulante et une ligne pour un champ de saisie.
  • Flexible en tant que parent de ListView : indique au framework de laisser la liste des messages reçus se développer pour remplir la hauteur de la Column tout en maintenant fixe la taille du TextField.
  • Divider : trace une ligne horizontale entre l'interface utilisateur pour l'affichage des messages et le champ de saisie de texte pour la rédaction des messages.
  • Container comme parent du compositeur du texte : définit les images de fond, la marge intérieure, les marges et d'autres détails de mise en page courants.
  • decoration : crée un objet BoxDecoration qui définit la couleur d'arrière-plan. Dans le cas présent, vous utilisez la cardColor définie par l'objet ThemeData du thème par défaut. L'arrière-plan de l'interface utilisateur pour la rédaction des messages sera ainsi différente de celle de la liste des messages.

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de l'application. Vous devriez obtenir un écran semblable à celui-ci :

Pixel 3XL

iPhone 11

b2f84ff91b0e1396.pngEnvoyez quelques messages de chat via les interfaces utilisateur de rédaction et d'affichage que vous venez de créer.

Pixel 3XL

iPhone 11

Des problèmes ?

Si votre application ne s'exécute pas correctement, vérifiez que vous n'avez pas fait de faute de frappe. Si nécessaire, utilisez le code disponible via le lien ci-dessous pour résoudre le problème.

Vous pouvez ajouter une animation à vos widgets pour offrir à l'utilisateur une expérience plus fluide et intuitive avec votre application. Dans cette section, vous allez apprendre à ajouter un effet d'animation de base à votre liste de messages de chat.

Lorsque l'utilisateur enverra un nouveau message de chat, au lieu de simplement l'afficher dans la liste des messages, vous animerez le message pour qu'il monte verticalement depuis le bas de l'écran.

Les animations de Flutter sont encapsulées sous la forme d'objets Animation qui contiennent une valeur saisie et un état (tel que forward, reverse, completed et dismissed). Vous pouvez associer un objet d'animation à un widget ou détecter les modifications apportées à cet objet. En fonction des modifications apportées aux propriétés de l'objet, le framework peut modifier l'apparence du widget et recompiler l'arborescence de widgets.

Spécifier un contrôleur d'animation

Utilisez la classe AnimationController pour spécifier le mode d'exécution de l'animation. L'AnimationController vous permet de définir des caractéristiques importantes de l'animation, comme sa durée et le sens de la lecture (avant ou arrière).

b2f84ff91b0e1396.pngModifiez la définition de la classe _ChatScreenState de sorte qu'elle inclue un TickerProviderStateMixin :

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {   // MODIFIED
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  ...

b2f84ff91b0e1396.pngDans la définition de classe ChatMessage, ajoutez une variable pour stocker le contrôleur d'animation :

class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text, required this.animationController}); // MODIFIED
  final String text;
  final AnimationController animationController;      // NEW
  ...

b2f84ff91b0e1396.pngAjoutez un contrôleur d'animation à la méthode _handleSubmitted() :

void _handleSubmitted(String text) {
  _textController.clear();
  var message = ChatMessage(
    text: text,
    animationController: AnimationController(      // NEW
      duration: const Duration(milliseconds: 700), // NEW
      vsync: this,                                 // NEW
    ),                                             // NEW
  );                                               // NEW
  setState(() {
    _messages.insert(0, message);
  });
  _focusNode.requestFocus();
  message.animationController.forward();           // NEW
}

cf1e10b838bf60ee.png Observations

  • L'AnimationController définit la durée d'exécution de l'animation sur 700 millisecondes. Cette durée plus longue ralentie l'effet d'animation afin que vous puissiez voir la transition s'effectuer plus progressivement. Dans la pratique, vous pouvez définir une durée plus courte lors de l'exécution de votre application.)
  • Le contrôleur d'animation est associé à une nouvelle instance ChatMessage et indique que l'animation doit être lue en avant chaque fois qu'un message est ajouté à la liste de chats.
  • Lorsque vous créez un AnimationController, vous devez lui transmettre un argument vsync. vsync correspond à la source des pulsations (Ticker) qui fait avancer l'animation. Dans cet exemple, _ChatScreenState est utilisé comme vsync. Il ajoute ainsi un mixin TickerProviderStateMixin à la définition de la classe _ChatScreenState.
  • Dans Dart, un mixin permet de réutiliser un corps de classe dans plusieurs hiérarchies de classes. Pour en savoir plus, consultez la section Ajouter des fonctionnalités à une classe : mixins de la présentation du langage Dart.

Ajouter un widget SizeTransition

L'ajout d'un widget SizeTransition à l'animation a pour effet d'animer un ClipRect qui affiche le texte de plus en plus à mesure qu'il glisse.

b2f84ff91b0e1396.pngAjoutez un widget SizeTransition à la méthode build() pour ChatMessage :

  1. Dans la méthode build() pour ChatMessage, sélectionnez la première instance Container.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu, puis sélectionnez Wrap with widget (Encapsuler avec un widget).
  3. Saisissez SizeTransition. Un cadre rouge apparaît autour de la propriété child:. Il indique qu'une propriété requise ne figure pas dans la classe widget. Pointez sur SizeTransition,. Une info-bulle indique que sizeFactor est obligatoire et vous propose de le créer. Sélectionnez cette option. La propriété apparaît avec une valeur null.
  4. Remplacez null par une instance CurvedAnimation. Cela ajoute le code récurrent pour deux propriétés : parent (obligatoire) et curve.
  5. Pour la propriété parent, remplacez null par le animationController.
  6. Pour la propriété curve, remplacez null par Curves.easeOut, l'une des constantes de la classe Curves.
  7. Ajoutez une ligne après sizeFactor (mais au même niveau), puis saisissez une propriété axisAlignment dans le widget SizeTransition, avec la valeur 0.0.
@override
Widget build(BuildContext context) {
  return SizeTransition(             // NEW
    sizeFactor:                      // NEW
        CurvedAnimation(parent: animationController, curve: Curves.easeOut),  // NEW
    axisAlignment: 0.0,              // NEW
    child: Container(                // MODIFIED
    ...

cf1e10b838bf60ee.png Observations

  • L'objet CurvedAnimation, combiné à la classe SizeTransition, produit un effet d'animation accéléré. Cet effet fait glisser le message rapidement vers le haut au début de l'animation et le ralentit jusqu'à ce qu'il s'arrête.
  • Le widget SizeTransition se comporte comme un ClipRect animé qui affiche une plus grande partie du texte à mesure qu'il glisse.

Supprimer l'animation

Pour libérer vos ressources, nous vous conseillons de supprimer les contrôleurs d'animation dès que vous n'en avez plus besoin.

b2f84ff91b0e1396.pngAjoutez la méthode dispose() à _ChatScreenState.

Ajoutez la méthode suivante au bas de _ChatScreenState :

@override
void dispose() {
  for (var message in _messages){
    message.animationController.dispose();
  }
  super.dispose();
}

b2f84ff91b0e1396.pngLe code est désormais correct, mais n'est pas bien formaté. Effectuez un clic droit dans le volet du code, puis sélectionnez Reformat Code with dartfmt (Reformater le code avec dartfmt).

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de l'application (ou un redémarrage à chaud si l'application en cours d'exécution contient des messages de chat), puis saisissez quelques messages pour observer l'effet d'animation.

Si vous voulez tester d'autres animations, voici quelques idées à essayer :

  • Accélérez ou ralentissez l'animation en modifiant la valeur duration spécifiée dans la méthode _handleSubmitted().
  • Spécifiez différentes courbes d'animation à l'aide des constantes définies dans la classe Curves.
  • Créez un effet d'animation de type fondu en encapsulant le Container dans un widget FadeTransition au lieu de SizeTransition.

Des problèmes ?

Si votre application ne s'exécute pas correctement, vérifiez que vous n'avez pas fait de faute de frappe. Si nécessaire, utilisez le code disponible via le lien ci-dessous pour résoudre le problème.

Pour cette étape facultative, vous allez attribuer à votre application quelques détails sophistiqués (comme l'activation du bouton Send (Envoyer) uniquement lorsqu'il y a du texte à envoyer, le retour à la ligne de messages plus longs et l'ajout d'éléments personnalisés à l'apparence native pour Android et iOS.

Rendre le bouton d'envoi actif en fonction du contexte

Actuellement, le bouton Send (Envoyer) est actif, même s'il n'y a pas de texte dans le champ de saisie. Toutefois, vous pouvez changer ce paramètre selon que le champ contienne ou non du texte à envoyer.

b2f84ff91b0e1396.pngDéfinissez _isComposing, une variable privée qui est vraie chaque fois que l'utilisateur saisit une chaîne dans le champ de saisie :

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isComposing = false;            // NEW

b2f84ff91b0e1396.pngAjoutez une méthode de rappel onChanged() à _ChatScreenState.

Dans la méthode _buildTextComposer(), ajoutez la propriété onChanged au TextField et modifiez la propriété onSubmitted :

Flexible(
  child: TextField(
    controller: _textController,
    onChanged: (String text) {            // NEW
      setState(() {                       // NEW
        _isComposing = text.isNotEmpty;   // NEW
      });                                 // NEW
    },                                    // NEW
    onSubmitted: _isComposing ? _handleSubmitted : null, // MODIFIED
    decoration:
        InputDecoration.collapsed(hintText: 'Send a message'),
    focusNode: _focusNode,
  ),
),

b2f84ff91b0e1396.pngModifiez la méthode de rappel onPressed() dans _ChatScreenState.

Toujours dans la méthode _buildTextComposer(), modifiez la propriété onPressed de l'IconButton :

Container(
  margin: EdgeInsets.symmetric(horizontal: 4.0),
  child: IconButton(
      icon: const Icon(Icons.send),
      onPressed: _isComposing                            // MODIFIED
          ? () => _handleSubmitted(_textController.text) // MODIFIED
          : null,                                        // MODIFIED
      )
      ...
)

b2f84ff91b0e1396.pngModifiez _handleSubmitted pour définir _isComposing sur "False" lorsque le champ de texte est effacé :

void _handleSubmitted(String text) {
  _textController.clear();
  setState(() {                             // NEW
    _isComposing = false;                   // NEW
  });                                       // NEW

  ChatMessage message = ChatMessage(
  ...

cf1e10b838bf60ee.png Observations

  • Le rappel onChanged indique au TextField que l'utilisateur a modifié le texte du champ. TextField appelle cette méthode chaque fois que sa valeur change de la valeur actuelle du champ.
  • Le rappel onChanged appelle setState() pour remplacer la valeur actuelle de _isComposing par "True" lorsque le champ contient du texte.
  • Lorsque _isComposing est défini sur "False", la propriété onPressed est définie sur null.
  • La propriété onSubmitted a également été modifiée de sorte qu'aucune chaîne vide ne soit ajoutée à la liste des messages.
  • La variable _isComposing contrôle désormais le comportement et l'affichage du bouton Send (Envoyer).
  • Si l'utilisateur saisit une chaîne dans le champ de texte, _isComposing est alors true,, et la couleur du bouton est définie sur Theme.of(context).accentColor. Lorsque l'utilisateur appuie sur le bouton Send (Envoyer), le framework appelle _handleSubmitted().
  • Si l'utilisateur ne saisit rien dans le champ de texte, _isComposing est alors false, et la propriété onPressed du widget est définie sur null, ce qui désactive le bouton Send (Envoyer). Le framework remplace automatiquement la couleur du bouton par Theme.of(context).disabledColor.

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de votre application pour essayer.

Faire revenir à la ligne le texte long

Lorsqu'un utilisateur envoie un message de chat qui dépasse la largeur d'affichage de l'interface utilisateur, le texte devrait retourner à la ligne afin que tout le message puisse être affiché. Pour le moment, le texte qui dépasse est tronqué et génère l'affichage d'une erreur. Pour être certain que le texte revienne correctement à la ligne, il suffit de le placer à l'intérieur d'un widget Expanded.

b2f84ff91b0e1396.pngEncapsulez le widget Column avec un widget Expanded :

  1. Dans la méthode build() pour ChatMessage, sélectionnez le widget Column à l'intérieur de la Row pour le Container.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu.
  3. Commencez à saisir Expanded, et sélectionnez Expanded (Étendu) dans la liste des objets possibles.

L'exemple de code suivant montre comment la classe ChatMessage se présente après cette modification :

...
Container(
  margin: const EdgeInsets.only(right: 16.0),
  child: CircleAvatar(child: Text(_name[0])),
),
Expanded(            // NEW
  child: Column(     // MODIFIED
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(_name, style: Theme.of(context).textTheme.headline4),
      Container(
        margin: EdgeInsets.only(top: 5.0),
        child: Text(text),
      ),
    ],
  ),
),                    // NEW
...

cf1e10b838bf60ee.png Observations

Le widget Expanded permet à son widget enfant (Column, par exemple) d'imposer des contraintes de mise en page (dans le cas présent, la largeur de la Column) à un widget enfant. Ici, il limite la largeur du widget Text, qui est normalement déterminée par son contenu.

Personnaliser l'interface utilisateur pour Android et iOS

Pour donner une apparence naturelle à l'interface utilisateur de votre application, vous pouvez ajouter un thème et une logique simple à la méthode build() pour la classe FriendlyChatApp. À cette étape, vous allez définir un thème de plate-forme qui applique un jeu différent de couleurs primaires et d'accentuation. Vous allez également personnaliser le bouton Send (Envoyer) de manière à utiliser un IconButton Material Design sur Android et un CupertinoButton sur iOS.

b2f84ff91b0e1396.pngAjoutez le code suivant dans main.dart, après la méthode main() :

final ThemeData kIOSTheme = ThemeData(
  primarySwatch: Colors.orange,
  primaryColor: Colors.grey[100],
  primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = ThemeData(
  primarySwatch: Colors.purple,
  accentColor: Colors.orangeAccent[400],
);

cf1e10b838bf60ee.png Observations

  • L'objet kDefaultTheme ThemeData spécifie les couleurs pour Android (violet avec des touches orange).
  • L'objet kIOSTheme ThemeData spécifie les couleurs pour iOS (gris clair avec des touches orange).

b2f84ff91b0e1396.pngModifiez la classe FriendlyChatApp pour changer le thème à l'aide de la propriété theme du widget MaterialApp de votre application :

  1. Importez le package de fondation en haut du fichier :
import 'package:flutter/foundation.dart';  // NEW
import 'package:flutter/material.dart';
  1. Modifiez la classe FriendlyChatApp pour choisir un thème approprié :
class FriendlyChatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FriendlyChat',
      theme: defaultTargetPlatform == TargetPlatform.iOS // NEW
        ? kIOSTheme                                      // NEW
        : kDefaultTheme,                                 // NEW
      home: ChatScreen(),
    );
  }
}

b2f84ff91b0e1396.pngModifiez le thème du widget AppBar (bannière en haut de l'interface utilisateur de votre application).

  1. Dans la méthode build() de _ChatScreenState, recherchez la ligne de code suivante :
      appBar: AppBar(title: Text('FriendlyChat')),
  1. Placez le curseur entre les deux parenthèses droites ())), saisissez une virgule, puis appuyez sur Entrée pour commencer une nouvelle ligne.
  2. Ajoutez les deux lignes suivantes :
elevation:
   Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
  1. Effectuez un clic droit dans le volet du code, puis sélectionnez Reformat Code with dartfmt (Reformater le code avec dartfmt).

cf1e10b838bf60ee.png Observations

  • La propriété defaultTargetPlatform de premier niveau et les opérateurs conditionnels sont utilisés pour sélectionner le thème.
  • La propriété elevation définit les coordonnées z de l'AppBar. Une valeur de coordonnée z de 4.0 a une ombre définie (Android), tandis qu'une valeur de 0.0 n'en a pas (iOS).

b2f84ff91b0e1396.pngPersonnalisez l'icône d'envoi pour Android et iOS.

  1. Ajoutez l'importation suivante en haut du fichier main.dart :
import 'package:flutter/cupertino.dart';   // NEW
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
  1. Dans la méthode _buildTextComposer() de _ChatScreenState, modifiez la ligne qui attribue un IconButton en tant qu'enfant du Container. Modifiez cette attribution de sorte qu'elle soit conditionnelle sur la plate-forme. Pour iOS, utilisez un CupertinoButton. Sinon, gardez IconButton :
Container(
   margin: EdgeInsets.symmetric(horizontal: 4.0),
   child: Theme.of(context).platform == TargetPlatform.iOS ? // MODIFIED
   CupertinoButton(                                          // NEW
     child: Text('Send'),                                    // NEW
     onPressed: _isComposing                                 // NEW
         ? () =>  _handleSubmitted(_textController.text)     // NEW
         : null,) :                                          // NEW
   IconButton(                                               // MODIFIED
       icon: const Icon(Icons.send),
       onPressed: _isComposing ?
           () =>  _handleSubmitted(_textController.text) : null,
       )
   ),

b2f84ff91b0e1396.pngEncapsulez la Column de premier niveau dans un widget Container et attribuez-lui une bordure gris clair sur son bord supérieur.

Cette bordure permet de distinguer visuellement la barre d'application du corps de l'application sur iOS. Pour masquer la bordure sur Android, appliquez la même logique que celle utilisée pour la barre d'application dans l'exemple de code précédent :

  1. Dans la méthode build() de _ChatScreenState, sélectionnez la Column qui apparaît après le body:.
  2. Appuyez sur Option+Return (macOS) ou sur Alt+Enter (Linux et Windows) pour afficher un menu, puis sélectionnez Wrap with Container (Encapsuler avec un conteneur).
  3. Après la fin de cette Column, mais avant la fin du Container, ajoutez le code (affiché) qui ajoute, de manière conditionnelle, le bouton approprié en fonction de la plate-forme.
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('FriendlyChat'),
      elevation:
          Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
    ),
    body: Container(
        child: Column(
          children: [
            Flexible(
              child: ListView.builder(
                padding: EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (_, int index) => _messages[index],
                itemCount: _messages.length,
              ),
            ),
            Divider(height: 1.0),
            Container(
              decoration: BoxDecoration(color: Theme.of(context).cardColor),
              child: _buildTextComposer(),
            ),
          ],
        ),
        decoration: Theme.of(context).platform == TargetPlatform.iOS // NEW
            ? BoxDecoration(                                 // NEW
                border: Border(                              // NEW
                  top: BorderSide(color: Colors.grey[200]!), // NEW
                ),                                           // NEW
              )                                              // NEW
            : null),                                         // MODIFIED
  );
}

b2f84ff91b0e1396.pngEffectuez une actualisation à chaud de l'application. Vous devriez voir des couleurs, des ombres et des boutons d'icône différents pour Android et iOS.

Pixel 3XL

iPhone 11

Des problèmes ?

Si votre application ne s'exécute pas correctement, vérifiez que vous n'avez pas fait de faute de frappe. Si nécessaire, utilisez le code disponible via le lien ci-dessous pour résoudre le problème.

Félicitations !

Vous maîtrisez maintenant les bases pour créer des applications mobiles multiplates-formes avec le framework Flutter.

Ce que vous avez appris

  • Développer une application Flutter en partant de zéro
  • Utiliser certains des raccourcis disponibles dans Android Studio et IntelliJ
  • Exécuter, actualiser à chaud et déboguer votre application Flutter sur un émulateur, un simulateur et un appareil
  • Personnaliser votre interface utilisateur à l'aide de widgets et d'animations
  • Personnaliser votre interface utilisateur pour Android et iOS

Étapes suivantes

Essayez l'un des autres ateliers de programmation Flutter.

Pour aller plus loin dans votre apprentissage de Flutter :

Pour en savoir plus sur les raccourcis clavier :

Vous pouvez télécharger l'exemple de code pour consulter les exemples à titre de référence ou démarrer l'atelier de programmation à partir d'une section spécifique. Pour obtenir une copie de l'exemple de code pour cet atelier de programmation, exécutez la commande suivante depuis votre terminal :

 git clone https://github.com/flutter/codelabs

L'exemple de code de cet atelier de programmation figure dans le dossier friendly_chat. Chaque dossier d'étape numérotée est aligné sur la représentation du code à la fin des étapes numérotées de cet atelier de programmation. Vous pouvez également déposer le code du fichier lib/main.dart de l'une de ces étapes dans une instance DartPad, puis l'exécuter à partir de là.