Navigation et pile "Retour"

1. Avant de commencer

Dans cet atelier de programmation, vous allez terminer l'implémentation de votre application Cupcake commencée dans un précédent atelier. L'application Cupcake possède plusieurs écrans et affiche un flux de commandes de cupcakes. Une fois terminée, l'application doit permettre à l'utilisateur de :

  • Créer une commande de cupcakes
  • Appuyer sur le bouton Haut ou Retour pour accéder à une étape précédente du flux de commande
  • Annuler une commande
  • Envoyer la commande à une autre application de messagerie.

Vous découvrirez comment Android gère les tâches et la pile "Retour" d'une application. Vous pouvez ainsi manipuler la pile "Retour" dans différents scénarios, comme l'annulation d'une commande, afin de ramener l'utilisateur au premier écran de l'application (au lieu de l'écran qui précède dans le flux de commande).

Conditions préalables

  • Vous êtes capable de créer et d'utiliser un ViewModel partagé pour l'ensemble des fragments d'une activité.
  • Vous maîtrisez le composant Navigation de Jetpack.
  • Vous avez déjà utilisé la liaison de données avec LiveData pour synchroniser l'UI avec le ViewModel.
  • Vous savez créer une intention pour démarrer une nouvelle activité.

Points abordés

  • L'impact de la navigation sur la pile "Retour" d'une application
  • Comment mettre en œuvre un comportement personnalisé pour la pile "Retour"

Objectifs de l'atelier

  • Créer une application de commande de cupcakes permettant à l'utilisateur d'envoyer la commande vers une autre application et de l'annuler.

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio
  • Disposer du code de l'application Cupcake provenant du précédent atelier.

2. Présentation de l'application de démarrage

Cet atelier de programmation utilise l'application Cupcake de l'atelier de programmation précédent. Vous pouvez soit utiliser votre code en terminant cet atelier de programmation, soit télécharger le code de démarrage depuis GitHub.

Télécharger le code de démarrage pour cet atelier de programmation

Si vous téléchargez le code de démarrage depuis GitHub, le nom du dossier du projet est android-basics-kotlin-cupcake-app-viewmodel. Sélectionnez ce dossier lorsque vous ouvrez le projet dans Android Studio.

Pour obtenir le code de cet atelier de programmation et l'ouvrir dans Android Studio, procédez comme suit :

Obtenir le code

  1. Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
  2. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une boîte de dialogue.

5b0a76c50478a73f.png

  1. Dans la boîte de dialogue, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open an existing Android Studio project (Ouvrir un projet Android Studio existant).

36cc44fcf0f89a1d.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > New > Import Project (Fichier > Nouveau > Importer un projet).

21f3eec988dcfbe9.png

  1. Dans la boîte de dialogue Import Project (Importer un projet), accédez à l'emplacement du dossier du projet décompressé. Il se trouve probablement dans le dossier Téléchargements.
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 11c34fc5e516fb1c.png pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.
  5. Parcourez les fichiers du projet dans la fenêtre de l'outil Project (Projet) pour voir comment l'application est configurée.

Exécutez l'application. Voici ce à quoi elle devrait ressembler :

45844688c0dc69a2.png

Au cours de cet atelier de programmation, vous allez d'abord terminer l'implémentation du bouton Up (haut) dans l'application. Ainsi, lorsque vous appuyez dessus, vous orientez l'utilisateur vers l'étape précédente du flux de commande.

fbdc1793f9fea6da.png

Vous allez ensuite ajouter un bouton Annuler afin que l'utilisateur puisse annuler la commande s'il change d'avis en cours de route.

d66fdafeac1b0dcf.gif

Ensuite, développez l'application de façon à ce que la commande soit partagée avec une autre application en appuyant sur Envoyer la commande à une autre application. La commande peut ensuite être envoyée par e-mail à une boutique de cupcakes, par exemple.

170d76b64ce78f56.png

C'est parti pour finaliser l'application Cupcake !

3. Comportement du bouton Up (Haut)

Dans l'application Cupcake, la barre d'application comporte une flèche qui permet de revenir à l'écran précédent. Il s'agit du bouton Up (Haut), déjà abordé lors des précédents ateliers. Le bouton Up (Haut) n'a aucun effet. Corrigez ce bug de navigation dans l'application.

fbdc1793f9fea6da.png

  1. Dans MainActivity, vous devriez déjà avoir du code pour configurer la barre d'application (également appelée barre d'action) avec le contrôleur de navigation. Définissez navController comme variable de classe afin de pouvoir l'utiliser dans une autre méthode.
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val navHostFragment = supportFragmentManager
                .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController

        setupActionBarWithNavController(navController)
    }
}
  1. Dans la même classe, ajoutez du code pour remplacer la fonction onSupportNavigateUp(). Ce code demandera à navController de gérer la navigation vers le haut dans l'application. Sinon, utilisez l'implémentation de la super-classe (dans AppCompatActivity) pour gérer le bouton Up (haut).
override fun onSupportNavigateUp(): Boolean {
   return navController.navigateUp() || super.onSupportNavigateUp()
}
  1. Exécutez l'application. Le bouton Up (Haut) devrait désormais fonctionner à partir de FlavorFragment, PickupFragment et SummaryFragment. Lorsque vous revenez aux étapes précédentes du flux de commande, les fragments doivent afficher la saveur et la date de retrait appropriées à partir du ViewModel.

4. Les tâches et la pile "Retour"

Vous allez maintenant ajouter un bouton Cancel (Annuler) dans le flux de commande de votre application. L'annulation d'une commande à tout moment du processus renvoie l'utilisateur vers StartFragment. Pour gérer ce comportement, vous allez découvrir les tâches et la pile "Retour" dans Android.

Les tâches

Les activités dans Android sont réparties dans des tâches. Lorsque vous ouvrez une application pour la première fois depuis l'icône du lanceur, Android crée une tâche comprenant votre activité principale. Une tâche est un ensemble d'activités avec lesquelles l'utilisateur interagit lorsqu'il effectue une tâche donnée (par exemple, consulter un e-mail, créer une commande de cupcake ou prendre une photo).

Les activités sont organisées dans une pile, appelée pile "Retour". Ainsi, chaque nouvelle activité de l'utilisateur est transférée vers la pile "Retour" de la tâche. Il s'agit d'une pile de pancakes, dans laquelle chaque nouveau pancake est ajouté. L'activité en haut de la pile correspond à l'activité avec laquelle l'utilisateur est en train d'interagir. Les activités en dessous de la pile ont été placées en arrière-plan et ont été arrêtées.

517054e483795b46.png

La pile "Retour" est utile lorsque l'utilisateur souhaite revenir en arrière dans sa navigation. Android peut supprimer l'activité actuelle du haut de la pile, l'effacer définitivement et recommencer l'activité en dessous. Ainsi, vous faites "sortir" une activité de la pile pour mettre l'activité précédente au premier plan et ainsi permettre à l'utilisateur d'interagir avec elle. Si l'utilisateur souhaite revenir en arrière à plusieurs reprises, Android maintient les activités en haut de la pile jusqu'à ce que vous vous rapprochiez du bas de la pile. Lorsqu'il n'y a plus d'activités dans la pile "Retour", l'utilisateur est redirigé vers le lanceur d'applications de l'appareil (ou vers l'application qui l'a lancé).

Examinons la version de l'application Words que vous avez implémentée avec deux activités : MainActivity et DetailActivity.

Lorsque vous lancez l'application pour la première fois, la MainActivity s'ouvre et est ajoutée à la pile "Retour" de la tâche.

4bc8f5aff4d5ee7f.png

Lorsque vous cliquez sur une lettre, l'élément DetailActivity est lancé et placé dans la pile "Retour". Ainsi, DetailActivity a été créé, démarré et réactivé pour que l'utilisateur puisse interagir avec lui. MainActivity est placé à l'arrière-plan et grisée dans le diagramme.

80f7c594ae844b84.png

Si vous appuyez sur le bouton Retour, l'élément DetailActivity est retiré de la pile "Retour", et l'instance DetailActivity est détruite.

80f532af817191a4.png

Ensuite, l'élément suivant au-dessus de la pile "Retour" (MainActivity) est placé au premier plan.

85004712d2fbcdc1.png

De la même manière que la pile "Retour" peut suivre les activités ouvertes par l'utilisateur, elle peut également suivre les destinations de fragment que l'utilisateur a visitées à l'aide du composant Navigation de Jetpack.

fe417ac5cbca4ce7.png

La bibliothèque Navigation vous permet de faire ressortir une destination de fragment de la pile "Retour" chaque fois que l'utilisateur clique sur le bouton Retour. Ce comportement par défaut ne présente aucun coût, vous n'avez pas besoin de l'implémenter. Vous ne devrez écrire du code que si vous avez besoin de personnaliser le comportement de la pile "Retour", ce que vous ferez pour l'application Cupcake.

Comportement par défaut de l'application Cupcake

Voyons comment fonctionne la pile "Retour" dans l'application Cupcake. L'application ne comporte qu'une seule activité, mais l'utilisateur navigue dans plusieurs destinations de fragment. Par conséquent, vous souhaitez que le bouton Retour revienne à une destination de fragment précédente chaque fois que l'utilisateur appuie dessus.

Lorsque vous ouvrez l'application pour la première fois, la destination StartFragment s'affiche. Cette destination est placée au-dessus de la pile.

cf0e80b4907d80dd.png

Après avoir sélectionné une quantité de cupcakes à commander, vous accédez au FlavorFragment, qui est placé dans la pile "Retour".

39081dcc3e537e1e.png

Lorsque vous sélectionnez un type de produit et que vous appuyez sur Suivant, vous accédez au PickupFragment, qui est placé dans la pile "Retour".

37dca487200f8f73.png

Enfin, après avoir sélectionné une date de retrait et appuyé sur Suivant, vous accédez au SummaryFragment, qui est ajouté en haut de la pile "Retour".

d67689affdfae0dd.png

Dans la SummaryFragment, imaginons que vous appuyez sur le bouton Retour ou Haut. Le SummaryFragment est retiré de la pile et détruit.

215b93fd65754017.png

Le PickupFragment se trouve maintenant au-dessus de la pile "Retour" et s'affiche à l'utilisateur.

37dca487200f8f73.png

Appuyez à nouveau sur le bouton Retour ou Haut. PickupFragment est retiré de la pile, puis FlavorFragment s'affiche.

Appuyez à nouveau sur le bouton Retour ou Haut. FlavorFragment est retiré de la pile, puis StartFragment s'affiche.

Lorsque vous revenez aux étapes précédentes du flux de commande, une seule destination s'affiche à la fois. Toutefois, dans la tâche suivante, vous allez ajouter une fonctionnalité d'annulation de commande dans l'application. Vous devrez peut-être afficher simultanément plusieurs destinations dans la pile "Retour" pour renvoyer l'utilisateur vers le StartFragment et commencer une nouvelle commande.

e3dae0f492450207.png

Modifier la pile "Retour" dans l'application Cupcake

Modifiez les classes et les fichiers de mise en page FlavorFragment, PickupFragment et SummaryFragment afin de proposer un bouton "Cancel order" (Annuler la commande) à l'utilisateur.

Ajouter une action de navigation

Commencez par ajouter des actions de navigation au graphique de navigation dans votre application, afin que l'utilisateur puisse revenir à StartFragment à partir des destinations suivantes.

  1. Ouvrez l'éditeur de navigation en accédant au fichier res > navigation > nav_graph.xml, puis en sélectionnant la vue Design (Conception).
  2. Il existe actuellement une action du startFragment au flavorFragment, une action du flavorFragment au pickupFragment et une action du pickupFragment au summaryFragment.
  3. Cliquez sur l'élément et faites-le glisser pour créer une action de navigation de summaryFragment à startFragment. Vous pouvez consulter ces instructions si vous avez besoin d'un petit rappel concernant l'association des destinations dans le graphique de navigation.
  4. Dans le pickupFragment, cliquez sur l'élément et faites-le glisser pour créer une action dans startFragment.
  5. Dans le flavorFragment, cliquez sur l'élément et faites-le glisser pour créer une action dans startFragment.
  6. Une fois que vous avez terminé, le graphique de navigation doit se présenter comme suit.

dcbd27a08d24cfa0.png

Grâce à ces modifications, l'utilisateur peut revenir au début du flux de commande à partir de l'un des fragments ultérieurs du flux de commande. Vous avez maintenant besoin de code permettant de naviguer selon ces actions. Le bouton Annuler convient parfaitement à cet usage.

Ajouter un bouton "Annuler" à la mise en page

Tout d'abord, ajoutez le bouton Annuler aux fichiers de mise en page pour tous les fragments, à l'exception de StartFragment. Il n'est pas nécessaire d'annuler une commande si vous êtes déjà sur le premier écran du flux de commande.

  1. Ouvrez le fichier de mise en page fragment_flavor.xml.
  2. Utilisez la vue Diviser pour modifier directement le code XML et afficher l'aperçu côte à côte.
  3. Ajoutez le bouton Annuler entre l'affichage de texte "sous-total" et le bouton Suivant. Attribuez-lui un ID de ressource @+id/cancel_button, avec le texte à afficher "@string/cancel".

Le bouton doit être positionné horizontalement à côté du bouton Suivant afin d'apparaître sous la forme d'une ligne de boutons. Pour appliquer une contrainte verticale, limitez la partie supérieure du bouton Annuler à la partie supérieure du bouton Suivant. Pour appliquer des contraintes horizontales, limitez le début du bouton Annuler au conteneur parent et son extrémité au début du bouton Suivant.

Définissez également une hauteur de wrap_content et une largeur de 0dp pour le bouton Annuler, afin qu'il puisse partager équitablement la largeur de l'écran avec l'autre bouton. Le bouton ne sera visible dans le volet Aperçu qu'à l'étape suivante.

...

<TextView
    android:id="@+id/subtotal" ... />

<Button
    android:id="@+id/cancel_button"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="@string/cancel"
    app:layout_constraintEnd_toStartOf="@id/next_button"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/next_button" />

<Button
    android:id="@+id/next_button" ... />

...
  1. Dans fragment_flavor.xml, vous devez également modifier la contrainte start du bouton Suivant, en la faisant passer de app:layout_constraintStart_toStartOf="parent à app:layout_constraintStart_toEndOf="@id/cancel_button". Ajoutez une marge de fin sur le bouton Annuler afin de laisser un espace blanc entre les deux boutons. Le bouton Cancel (Annuler) devrait maintenant apparaître dans le volet Preview (Aperçu) d'Android Studio.
...

<Button
    android:id="@+id/cancel_button"
    android:layout_marginEnd="@dimen/side_margin" ... />

<Button
    android:id="@+id/next_button"
    app:layout_constraintStart_toEndOf="@id/cancel_button"... />

...
  1. Pour le visuel, appliquez le style OutlinedButton de Material Design (Bouton avec contour de Material Design), avec l'attribut style="?attr/materialButtonOutlinedStyle", de sorte que le bouton Annuler reste moins visible que Suivant, qui est l'action principale.
<Button
    android:id="@+id/cancel_button"
    style="?attr/materialButtonOutlinedStyle" ... />

Le bouton est élégant et bien positionné désormais.

1fb41763cc255c05.png

  1. De la même manière, ajoutez un bouton Annuler au fichier de mise en page fragment_pickup.xml.
...

<TextView
    android:id="@+id/subtotal" ... />

<Button
    android:id="@+id/cancel_button"
    style="?attr/materialButtonOutlinedStyle"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="@dimen/side_margin"
    android:text="@string/cancel"
    app:layout_constraintEnd_toStartOf="@id/next_button"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/next_button" />

<Button
    android:id="@+id/next_button" ... />

...
  1. Mettez également à jour la contrainte start du bouton Suivant. Le bouton Annuler s'affiche alors dans l'aperçu.
<Button
    android:id="@+id/next_button"
    app:layout_constraintStart_toEndOf="@id/cancel_button" ... />
  1. Apportez une modification similaire au fichier fragment_summary.xml, même si la mise en page de ce fragment est légèrement différente. Vous allez ajouter le bouton Annuler sous le bouton Envoyer dans le layout vertical parent LinearLayout en laissant une certaine marge.

741c0f034397795c.png

...

    <Button
        android:id="@+id/send_button" ... />

    <Button
        android:id="@+id/cancel_button"
        style="?attr/materialButtonOutlinedStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_between_elements"
        android:text="@string/cancel" />

</LinearLayout>
  1. Exécutez et testez l'application. Le bouton Annuler doit s'afficher dans les mises en page pour FlavorFragment, PickupFragment et SummaryFragment. Lorsque vous appuyez sur le bouton, rien ne se passe. Vous allez configurer les écouteurs de clics pour ces boutons à l'étape suivante.

Ajouter un écouteur de clics au bouton "Annuler"

Dans chaque classe de fragment (sauf StartFragment), ajoutez une méthode d'assistance qui gère le clic sur le bouton Annuler.

  1. Ajoutez cette méthode cancelOrder() à FlavorFragment. Si l'utilisateur décide d'annuler sa commande, il doit effacer le ViewModel au moment de choisir les saveurs, en appelant sharedViewModel.resetOrder().. Revenez ensuite au StartFragment en utilisant l'action de navigation associée à l'ID R.id.action_flavorFragment_to_startFragment..
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_flavorFragment_to_startFragment)
}

Si vous rencontrez une erreur liée à l'ID de ressource de l'action, vous devrez peut-être revenir au fichier nav_graph.xml pour vérifier que vos actions de navigation portent le même nom (action_flavorFragment_to_startFragment).

  1. Utilisez la liaison d'écouteur pour configurer l'écouteur de clics sur le bouton Annuler dans la mise en page fragment_flavor.xml. Cliquez sur ce bouton pour appeler la méthode cancelOrder() que vous venez de créer dans la classe FragmentFlavor.
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> flavorFragment.cancelOrder()}" ... />
  1. Répétez le processus pour PickupFragment. Ajoutez une méthode cancelOrder() à la classe de fragment, qui réinitialise l'ordre et passe de PickupFragment à StartFragment.
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_pickupFragment_to_startFragment)
}
  1. Dans fragment_pickup.xml, définissez l'écouteur de clics sur le bouton Annuler pour appeler la méthode cancelOrder() lorsque vous cliquez dessus.
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> pickupFragment.cancelOrder()}" ... />
  1. Ajoutez un code similaire pour le bouton Annuler dans le SummaryFragment. L'utilisateur est redirigé vers StartFragment. Vous devrez peut-être importer androidx.navigation.fragment.findNavController si ce n'est pas fait automatiquement.
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_summaryFragment_to_startFragment)
}
  1. Dansfragment_summary.xml, appelez la méthode cancelOrder() du SummaryFragment lorsque que l'utilisateur clique sur Cancel (Annuler).
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> summaryFragment.cancelOrder()}" ... />
  1. Exécutez et testez l'application pour vérifier la logique que vous venez d'ajouter à chaque fragment. Lorsque vous créez une commande de cupcake, appuyez sur le bouton Annuler de FlavorFragment, PickupFragment ou SummaryFragment pour revenir au StartFragment. En créant une commande, vous remarquerez que les informations de la commande précédente ont été effacées.

Il semblerait que ça fonctionne, mais un problème est survenu avec la navigation vers l'arrière, lorsque vous êtes revenu au StartFragment. Suivez les étapes suivantes afin de reproduire le bug.

  1. Suivez la procédure de création d'une commande de cupcake jusqu'à l'écran récapitulatif. Par exemple, vous pouvez commander 12 cupcakes et différentes saveurs de chocolat, puis choisir une date future pour le retrait.
  2. Appuyez ensuite sur Annuler. Revenez au StartFragment.
  3. Le résultat a l'air correct, mais si vous appuyez sur le bouton Retour du système, vous retournez à l'écran récapitulatif de la commande qui indique une quantité à 0 cupcakes et s'affiche pas de saveur. Cette information est incorrecte et ne devrait pas s'afficher à l'utilisateur.

1a9024cd58a0e643.png

L'utilisateur ne souhaitera probablement pas revenir au flux de commande. De plus, toutes les données de commande du ViewModel ont été effacées. Ces informations ne sont donc pas utiles. Appuyez plutôt sur le bouton Retour de l'application StartFragment pour quitter l'application Cupcake.

Voyons à quoi ressemble la pile "Retour" et comment corriger ce problème. Lorsque vous créez une commande depuis l'écran récapitulatif, chaque destination est ajoutée à la pile "Retour".

fc88100cdf1bdd1.png

Dans le SummaryFragment, vous avez annulé la commande. Lorsque vous avez navigué de l'action SummaryFragment à StartFragment, Android a ajouté une autre instance de StartFragment en tant que nouvelle destination dans la pile "Retour".

5616cb0028b63602.png

C'est pourquoi, lorsque vous avez appuyé sur le bouton Retour à partir du StartFragment, l'application a fini par afficher l'icône SummaryFragment (avec des informations de commande vides).

Pour résoudre ce problème de navigation, découvrez comment le composant Navigation vous permet d'afficher des destinations supplémentaires depuis la pile "Retour" lorsque vous utilisez la fonctionnalité de navigation.

Faire sortir les destinations supplémentaires de la pile "Retour"

En incluant un attribut app:popUpTo à l'action de navigation dans le graphique de navigation, vous pouvez faire apparaître plusieurs destinations dans la pile "Retour" jusqu'à ce que la destination spécifiée soit atteinte. Si vous spécifiez app:popUpTo="@id/startFragment", les destinations de la pile "Retour" sont retirées jusqu'à ce que vous atteigniez StartFragment, qui restera dans la pile.

Si vous apportez cette modification au code et exécutez l'application, vous constaterez qu'en annulant une commande, vous revenez au StartFragment. Mais cette fois, lorsque vous appuyez sur le bouton Retour du StartFragment, le StartFragment s'affiche à nouveau et vous ne quittez pas l'application. Ce comportement n'est pas non plus souhaitable. Comme indiqué précédemment, comme vous naviguez vers StartFragment, Android ajoute StartFragment en tant que nouvelle destination dans la pile "Retour". Vous disposez maintenant de deux instances de StartFragment dans la pile "Retour". Vous devez donc appuyer deux fois sur le bouton Retour pour quitter l'application.

dd0fedc6e231e595.png

Pour corriger ce nouveau bug, demandez à ce que toutes les destinations soient placées en dehors de la pile "Retour" jusqu'au StartFragment inclus. Pour ce faire, spécifiez app:popUpTo="@id/startFragment"

et app:popUpToInclusive="true" sur les actions de navigation appropriées. Ainsi, seule la nouvelle instance de StartFragment sera présente dans la pile "Retour". Ensuite, appuyer sur Retour une fois dans le StartFragment vous fera quitter l'application. Apportons cette modification maintenant.

cf0e80b4907d80dd.png

Modifier les actions de navigation

  1. Accédez à l'éditeur de navigation en ouvrant le fichier res > navigation > nav_graph.xml.
  2. Sélectionnez l'action qui va de summaryFragment à startFragment pour qu'elle soit mise en surbrillance (bleu).
  3. Développez la section Attributs à droite (si elle n'est pas déjà ouverte). Recherchez Pop Behavior (comportement des pops) dans la liste des attributs modifiables.

8c87589f9cc4d176.png

  1. Dans les options du menu déroulant, définissez popUpTo sur startFragment. Cela signifie que toutes les destinations de la pile "Retour" seront générées (en partant du haut de la pile et vers le bas), jusqu'au startFragment.

a9a17493ed6bc27f.png

  1. Ensuite, cochez la case popUpToInclusive jusqu'à ce qu'une coche et le libellé true s'affichent. Vous indiquez ainsi que vous souhaitez extraire des destinations jusqu'à l'instance de startFragment qui se trouve déjà dans la pile "Retour". Vous n'aurez alors pas deux instances de startFragment dans la pile "Retour".

4a403838a62ff487.png

  1. Répétez ces modifications pour l'action qui connecte pickupFragment à startFragment.

4a403838a62ff487.png

  1. Répétez l'opération pour l'action qui connecte flavorFragment à startFragment.
  2. Lorsque vous avez terminé, vérifiez que vous avez apporté les modifications nécessaires à votre application en consultant la vue Code du fichier du graphique de navigation.
<navigation
    android:id="@+id/nav_graph" ...>
    <fragment
        android:id="@+id/startFragment" ...>
        ...
    </fragment>
    <fragment
        android:id="@+id/flavorFragment" ...>
        ...
        <action
            android:id="@+id/action_flavorFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/pickupFragment" ...>
        ...
        <action
            android:id="@+id/action_pickupFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/summaryFragment" ...>
        <action
            android:id="@+id/action_summaryFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
</navigation>

Notez que pour chacune des trois actions (action_flavorFragment_to_startFragment, action_pickupFragment_to_startFragment et action_summaryFragment_to_startFragment), vous devez ajouter les attributs app:popUpTo="@id/startFragment" et app:popUpToInclusive="true".

  1. Exécutez maintenant l'application. Parcourez le flux de commande, puis appuyez sur Annuler. Lorsque vous retournez au StartFragment, appuyez une seule fois sur le bouton Retour pour quitter l'application.

Résumons. Lorsque vous avez annulé la commande et que vous êtes revenu au premier écran de l'application, toutes les destinations de fragment dans la pile "Retour" ont été retirées de la pile, y compris la première instance de StartFragment. Une fois l'action de navigation terminée, le StartFragment a été ajouté en tant que nouvelle destination dans la pile "Retour". Ensuite, lorsque vous appuyez sur Retour, vous effacez le StartFragment de la pile. Il ne reste plus aucun fragment dans la pile "Retour". Android termine donc l'activité et l'utilisateur quitte l'application.

L'application devrait se présenter comme suit : 2e0599d9b55401f1.png

5. Envoyer la commande

L'application est beaucoup plus élégante maintenant ! Toutefois, une partie manque encore. Lorsque vous appuyez sur le bouton d'envoi de la commande SummaryFragment, un message Toast s'affiche encore.

90ed727c7b812fd6.png

Il serait plus judicieux d'envoyer la commande depuis l'application. Utilisez ce que vous avez appris dans les précédents ateliers de programmation sur l'utilisation d'un intent implicite pour partager des informations de votre application avec une autre application. Ainsi, l'utilisateur peut partager les informations concernant la commande de cupcakes avec une application de messagerie installée sur son appareil, et pourra ainsi envoyer la commande par e-mail à la boutique concernée.

170d76b64ce78f56.png

Pour implémenter cette fonctionnalité, examinez la structure de l'objet et du corps d'e-mail dans la capture d'écran ci-dessus.

Vous utiliserez les chaînes qui se trouvent déjà dans votre fichier strings.xml.

<string name="new_cupcake_order">New Cupcake Order</string>
<string name="order_details">Quantity: %1$s cupcakes \n Flavor: %2$s \nPickup date: %3$s \n Total: %4$s \n\n Thank you!</string>

order_details est une ressource de chaîne comportant quatre arguments de format différents, qui sont des espaces réservés pour la quantité réelle de cupcakes, la saveur souhaitée, la date de retrait souhaitée et le prix total. Les arguments sont numérotés de 1 à 4 avec une syntaxe de %1 à %4. Le type d'argument est également spécifié ($s signifie qu'une chaîne est attendue ici).

Dans le code en Kotlin, vous pouvez appeler getString() sur R.string.order_details, suivi des quatre arguments (l'ordre est important !). Par exemple, appeler getString(R.string.order_details, "12", "Chocolate", "Sat Dec 12", "$24.00") crée la chaîne suivante, qui correspond exactement au corps d'e-mail souhaité.

Quantity: 12 cupcakes
Flavor: Chocolate
Pickup date: Sat Dec 12
Total: $24.00

Thank you!
  1. Dans SummaryFragment.kt, modifiez la méthode sendOrder(). Supprimez le message Toast existant.
fun sendOrder() {

}
  1. Dans la méthode sendOrder(), créez le texte récapitulatif de la commande. Créez la chaîne order_details mise en forme en obtenant la quantité, le type, la date et le prix de la commande à partir du modèle de vue partagée.
val orderSummary = getString(
    R.string.order_details,
    sharedViewModel.quantity.value.toString(),
    sharedViewModel.flavor.value.toString(),
    sharedViewModel.date.value.toString(),
    sharedViewModel.price.value.toString()
)
  1. Toujours dans la méthode sendOrder(), créez un intent implicite pour partager la commande avec une autre application. Consultez la documentation pour découvrir comment créer un intent d'e-mail. Spécifiez Intent.ACTION_SEND pour l'action d'intent, définissez le type sur "text/plain" et ajoutez des extras à cet intent pour l'objet (Intent.EXTRA_SUBJECT) et le corps d'e-mail (Intent.EXTRA_TEXT). Importez android.content.Intent si nécessaire.
val intent = Intent(Intent.ACTION_SEND)
    .setType("text/plain")
    .putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
    .putExtra(Intent.EXTRA_TEXT, orderSummary)

Astuce : si vous adaptez cette application à votre utilisation, vous pouvez préremplir l'adresse de la boutique de cupcakes en tant que destinataire de l'e-mail. Dans l'intent, vous devez spécifier le destinataire de l'e-mail avec l'intent supplémentaire Intent.EXTRA_EMAIL.

  1. Comme il s'agit d'un intent implicite, vous n'avez pas besoin de savoir à l'avance quel composant ou quelle application gérera cet intent. L'utilisateur décide quelle application il souhaite utiliser pour remplir l'intent. Toutefois, avant de lancer une activité avec cet intent, vérifiez s'il existe une application capable de la gérer. Cette vérification empêche le plantage de l'application Cupcake s'il n'existe aucune application pouvant gérer l'intent, ce qui renforce la sécurité de votre code.
if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
    startActivity(intent)
}

Pour effectuer cette vérification, accédez à PackageManager, qui contient des informations sur les packages d'applications installés sur l'appareil. Vous pouvez accéder à PackageManager via l'élément activity du fragment, à condition que activity et packageManager n'aient pas une valeur nulle. Appelez la méthode resolveActivity() de PackageManager avec l'intent que vous avez créé. Si le résultat n'est pas une valeur nulle, vous pouvez appeler startActivity() sans risque.

  1. Exécutez votre application pour tester votre code. Créez une commande de cupcake et appuyez sur Envoyer la commande à une autre application. Lorsque la fenêtre de partage s'affiche, sélectionnez l'application Gmail, ou une autre application selon vos préférences. Si vous choisissez l'application Gmail, vous devrez peut-être configurer un compte sur l'appareil, si ce n'est pas déjà fait (p. ex. si vous utilisez l'émulateur). Si votre dernière commande de cupcakes n'apparaît pas dans le corps d'e-mail, vous devrez peut-être d'abord supprimer le brouillon actuel.

170d76b64ce78f56.png

Testez différents scénarios. Vous remarquerez peut-être un bug si un seul cupcake s'affiche. Le récapitulatif de la commande indique "1 cupcakes", mais en anglais, cette erreur est incorrecte sur le plan grammatical.

ef046a100381bb07.png

À la place, vous devez saisir 1 cupcake (pas de pluriel). Si vous souhaitez choisir le mot cupcake ou cupcakes en fonction de la valeur de la quantité, vous pouvez utiliser des chaînes de quantité dans Android. En déclarant une ressource plurals, vous pouvez spécifier différentes ressources de chaîne à utiliser en fonction de la quantité, par exemple au singulier ou au pluriel.

  1. Ajoutez une ressource cupcakes au pluriel dans votre fichier strings.xml.
<plurals name="cupcakes">
    <item quantity="one">%d cupcake</item>
    <item quantity="other">%d cupcakes</item>
</plurals>

Pour un nom singulier (quantity="one"), on utilisera la chaîne de singulier. Dans tous les autres cas (quantity="other"), on utilisera la chaîne de pluriel. Notez qu'au lieu de %s, qui attend un argument de chaîne, %d attend un argument de nombre entier, que vous transmettrez lorsque vous mettrez en forme la chaîne.

Dans votre code en Kotlin, appelez :

getQuantityString(R.plurals.cupcakes, 1, 1) renvoie la chaîne 1 cupcake.

getQuantityString(R.plurals.cupcakes, 6, 6) renvoie la chaîne 6 cupcakes.

getQuantityString(R.plurals.cupcakes, 0, 0) renvoie la chaîne 0 cupcakes.

  1. Avant d'accéder à votre code Kotlin, mettez à jour la ressource de chaîne order_details dans strings.xml pour que la version plurielle de cupcakes ne soit plus codée en dur.
<string name="order_details">Quantity: %1$s \n Flavor: %2$s \nPickup date: %3$s \n
        Total: %4$s \n\n Thank you!</string>
  1. Dans la classe SummaryFragment, mettez à jour votre méthode sendOrder() pour qu'elle utilise la nouvelle chaîne de quantité. Il serait plus judicieux de déterminer la quantité à partir du ViewModel et de la stocker dans une variable. Étant donné que quantity dans le ViewModel est de type LiveData<Int>, il est possible que sharedViewModel.quantity.value ait une valeur nulle. Si la quantité est nulle, attribuez la valeur par défaut 0 à numberOfCupcakes.

Ajoutez ceci à la première ligne de code de votre méthode sendOrder().

val numberOfCupcakes = sharedViewModel.quantity.value ?: 0

La présence de l'opérateur Elvis (?:) indique l'utilisation de l'expression de gauche si elle n'a pas une valeur nulle. Toutefois, si l'expression de gauche a une valeur nulle, c'est l'expression qui se trouve à droite de l'opérateur Elvis qui sera utilisée (0 ici).

  1. Mettez ensuite en forme la chaîne order_details comme vous l'avez fait précédemment. Au lieu de transmettre directement numberOfCupcakes en tant qu'argument de quantité, créez la chaîne de cupcakes mise en forme avec resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes).

La méthode sendOrder() complète doit se présenter comme suit :

fun sendOrder() {
    val numberOfCupcakes = sharedViewModel.quantity.value ?: 0
    val orderSummary = getString(
        R.string.order_details,
        resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes),
        sharedViewModel.flavor.value.toString(),
        sharedViewModel.date.value.toString(),
        sharedViewModel.price.value.toString()
    )

    val intent = Intent(Intent.ACTION_SEND)
        .setType("text/plain")
        .putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
        .putExtra(Intent.EXTRA_TEXT, orderSummary)

    if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
        startActivity(intent)
    }
}
  1. Exécutez et testez votre code. Vérifiez que le récapitulatif de la commande dans le corps d'e-mail affiche 1 cupcake au lieu de 6 cupcakes ou 12 cupcakes.

Vous avez maintenant terminé toutes les fonctionnalités de votre application Cupcake ! Félicitations ! Coder cette appli était un véritable défi, mais vous avez fait d'énormes progrès et êtes sur la bonne voie pour devenir un développeur Android  Vous avez réussi à intégrer tous les concepts que vous avez acquis jusqu'à présent, et avez reçu de nouveaux conseils pour résoudre des problèmes en cours de route.

Dernières étapes

Prenez maintenant le temps de nettoyer votre code, une recommandation apprise lors des précédents ateliers de programmation.

  • Optimisez les importations.
  • Reformatez les fichiers.
  • Supprimer le code inutilisé ou commenté
  • Ajoutez des commentaires dans le code si nécessaire.

Pour rendre votre application plus accessible, testez-la en activant Talkback afin d'optimiser l'expérience utilisateur. Les commentaires audio doivent indiquer l'objectif de chaque élément à l'écran, le cas échéant. Assurez-vous également que l'utilisateur peut accéder à tous les éléments de l'application à l'aide de gestes de balayage.

Vérifiez que tous les cas d'utilisation que vous avez implémentés fonctionnent comme prévu dans votre application finale. Exemples :

  • Les données doivent être conservées lors de la rotation de l'appareil (grâce au ViewModel).
  • Si vous appuyez sur le bouton Haut ou Retour, les informations de la commande doivent toujours s'afficher correctement sur FlavorFragment et PickupFragment.
  • Si vous envoyez la commande à une autre application, les informations de la commande doivent être correctes.
  • L'annulation d'une commande doit effacer toutes les informations associées à celle-ci.

Si vous identifiez des bugs, corrigez-les.

Bravo, la double vérification de votre travail est maintenant terminée !

6. Code de solution

Le code de solution de cet atelier de programmation figure dans le projet ci-dessous.

Pour obtenir le code de cet atelier de programmation et l'ouvrir dans Android Studio, procédez comme suit :

Obtenir le code

  1. Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
  2. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une boîte de dialogue.

5b0a76c50478a73f.png

  1. Dans la boîte de dialogue, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open an existing Android Studio project (Ouvrir un projet Android Studio existant).

36cc44fcf0f89a1d.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > New > Import Project (Fichier > Nouveau > Importer un projet).

21f3eec988dcfbe9.png

  1. Dans la boîte de dialogue Import Project (Importer un projet), accédez à l'emplacement du dossier du projet décompressé. Il se trouve probablement dans le dossier Téléchargements.
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 11c34fc5e516fb1c.png pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.
  5. Parcourez les fichiers du projet dans la fenêtre de l'outil Projet pour voir comment l'application est configurée.

7. Résumé

  • Android conserve une pile "Retour" de toutes les destinations que vous avez visitées, chaque nouvelle destination étant insérée dans la pile.
  • Appuyez sur le bouton Haut ou Retour pour faire sortir les destinations de la pile "Retour".
  • L'utilisation du composant Navigation de Jetpack vous aide à transférer les destinations de fragment et à les faire ressortir de la pile "Retour". Ainsi, le comportement par défaut du bouton Retour ne vous coûte rien.
  • Spécifiez l'attribut app:popUpTo sur une action du graphique de navigation afin de faire sortir les destinations de la pile "Retour" jusqu'à celle spécifiée dans la valeur de l'attribut.
  • Spécifiez app:popUpToInclusive="true" pour une action lorsque la destination spécifiée dans app:popUpTo doit également être placée en dehors de la pile "Retour".
  • Vous pouvez créer un intent implicite pour partager du contenu avec une application de messagerie, à l'aide de Intent.ACTION_SEND et en insérant des extras d'intent tels que Intent.EXTRA_EMAIL, Intent.EXTRA_SUBJECT et Intent.EXTRA_TEXT, pour n'en citer que quelques-uns.
  • Utilisez une ressource plurals si vous souhaitez utiliser différentes ressources de chaîne en fonction de la quantité, comme le singulier ou le pluriel.

8. En savoir plus

9. Pour s'entraîner

Développez l'application Cupcake en ajoutant vos propres variantes dans le flux de commande des cupcakes. Exemples :

  • Proposez une saveur spéciale associée à des conditions spécifiques (p. ex. ne pas être disponible pour le retrait le jour même).
  • Demandez à l'utilisateur de vous fournir le nom de leur commande de cupcakes.
  • Autorisez l'utilisateur à sélectionner plusieurs goûts de cupcake pour sa commande si la quantité est supérieure à un cupcake.

Quels aspects de votre application devez-vous mettre à jour pour intégrer de cette nouvelle fonctionnalité ?

Vérifiez votre travail :

Une fois terminée, votre application devrait fonctionner sans erreur.

10. Défi

Utilisez ce que vous avez appris en créant l'application Cupcake pour créer votre propre application. Il peut s'agir d'une application de commande de pizzas, de sandwichs, de tout ce que vous voulez ! Nous vous recommandons de réfléchir aux différentes destinations de votre application avant de commencer à l'implémenter.

Pour vous inspirer d'autres idées de design, vous pouvez également consulter l'application Shrine, une étude Material qui vous montre comment intégrer les thèmes et composants Material à votre propre marque. L'application Shrine est beaucoup plus complexe que l'application Cupcake que vous avez développée. Au lieu de viser la création d'une application très compliquée, réfléchissez aux petites fonctionnalités avec lesquelles vous pouvez commencer. Prenez confiance en remportant de petites victoires progressives.

Une fois que vous avez créé votre propre application, partagez votre création sur les réseaux sociaux. Utilisez le hashtag #LearningKotlin pour le montrer !