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
- Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une boîte de dialogue.
- 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.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- 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
- Lancez Android Studio.
- 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).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > New > Import Project (Fichier > Nouveau > Importer un projet).
- 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.
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.
- 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 :
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.
Vous allez ensuite ajouter un bouton Annuler afin que l'utilisateur puisse annuler la commande s'il change d'avis en cours de route.
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.
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.
- 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éfinisseznavController
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)
}
}
- 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 (dansAppCompatActivity
) pour gérer le bouton Up (haut).
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
- Exécutez l'application. Le bouton Up (Haut) devrait désormais fonctionner à partir de
FlavorFragment
,PickupFragment
etSummaryFragment
. 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.
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.
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.
Si vous appuyez sur le bouton Retour, l'élément DetailActivity
est retiré de la pile "Retour", et l'instance DetailActivity
est détruite.
Ensuite, l'élément suivant au-dessus de la pile "Retour" (MainActivity
) est placé au premier plan.
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.
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.
Après avoir sélectionné une quantité de cupcakes à commander, vous accédez au FlavorFragment
, qui est placé dans la pile "Retour".
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".
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".
Dans la SummaryFragment
, imaginons que vous appuyez sur le bouton Retour ou Haut. Le SummaryFragment
est retiré de la pile et détruit.
Le PickupFragment
se trouve maintenant au-dessus de la pile "Retour" et s'affiche à l'utilisateur.
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.
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.
- Ouvrez l'éditeur de navigation en accédant au fichier res > navigation > nav_graph.xml, puis en sélectionnant la vue Design (Conception).
- Il existe actuellement une action du
startFragment
auflavorFragment
, une action duflavorFragment
aupickupFragment
et une action dupickupFragment
ausummaryFragment
. - 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. - Dans le
pickupFragment
, cliquez sur l'élément et faites-le glisser pour créer une action dansstartFragment
. - Dans le
flavorFragment
, cliquez sur l'élément et faites-le glisser pour créer une action dansstartFragment
. - Une fois que vous avez terminé, le graphique de navigation doit se présenter comme suit.
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.
- Ouvrez le fichier de mise en page
fragment_flavor.xml
. - Utilisez la vue Diviser pour modifier directement le code XML et afficher l'aperçu côte à côte.
- 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" ... />
...
- Dans
fragment_flavor.xml
, vous devez également modifier la contrainte start du bouton Suivant, en la faisant passer deapp: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"... />
...
- 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.
- 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" ... />
...
- 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" ... />
- 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 parentLinearLayout
en laissant une certaine marge.
...
<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>
- Exécutez et testez l'application. Le bouton Annuler doit s'afficher dans les mises en page pour
FlavorFragment
,PickupFragment
etSummaryFragment
. 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.
- 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 appelantsharedViewModel.resetOrder().
. Revenez ensuite auStartFragment
en utilisant l'action de navigation associée à l'IDR.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
).
- 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éthodecancelOrder()
que vous venez de créer dans la classeFragmentFlavor
.
<Button
android:id="@+id/cancel_button"
android:onClick="@{() -> flavorFragment.cancelOrder()}" ... />
- Répétez le processus pour
PickupFragment
. Ajoutez une méthodecancelOrder()
à la classe de fragment, qui réinitialise l'ordre et passe dePickupFragment
àStartFragment
.
fun cancelOrder() {
sharedViewModel.resetOrder()
findNavController().navigate(R.id.action_pickupFragment_to_startFragment)
}
- Dans
fragment_pickup.xml
, définissez l'écouteur de clics sur le bouton Annuler pour appeler la méthodecancelOrder()
lorsque vous cliquez dessus.
<Button
android:id="@+id/cancel_button"
android:onClick="@{() -> pickupFragment.cancelOrder()}" ... />
- Ajoutez un code similaire pour le bouton Annuler dans le
SummaryFragment
. L'utilisateur est redirigé versStartFragment
. Vous devrez peut-être importerandroidx.navigation.fragment.findNavController
si ce n'est pas fait automatiquement.
fun cancelOrder() {
sharedViewModel.resetOrder()
findNavController().navigate(R.id.action_summaryFragment_to_startFragment)
}
- Dans
fragment_summary.xml
, appelez la méthodecancelOrder()
duSummaryFragment
lorsque que l'utilisateur clique sur Cancel (Annuler).
<Button
android:id="@+id/cancel_button"
android:onClick="@{() -> summaryFragment.cancelOrder()}" ... />
- 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
ouSummaryFragment
pour revenir auStartFragment
. 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.
- 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.
- Appuyez ensuite sur Annuler. Revenez au
StartFragment
. - 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.
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".
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".
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"
Action de navigation : attribut popUpTo
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.
Action de navigation : attribut popUpToInclusive
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.
Modifier les actions de navigation
- Accédez à l'éditeur de navigation en ouvrant le fichier res > navigation > nav_graph.xml.
- Sélectionnez l'action qui va de
summaryFragment
àstartFragment
pour qu'elle soit mise en surbrillance (bleu). - 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.
- 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'austartFragment
.
- 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 destartFragment
dans la pile "Retour".
- Répétez ces modifications pour l'action qui connecte
pickupFragment
àstartFragment
.
- Répétez l'opération pour l'action qui connecte
flavorFragment
àstartFragment
. - 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"
.
- 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 :
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.
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.
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!
- Dans
SummaryFragment.kt
, modifiez la méthodesendOrder()
. Supprimez le messageToast
existant.
fun sendOrder() {
}
- Dans la méthode
sendOrder()
, créez le texte récapitulatif de la commande. Créez la chaîneorder_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()
)
- 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écifiezIntent.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
). Importezandroid.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
.
- 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.
- 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.
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.
À 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.
- Ajoutez une ressource
cupcakes
au pluriel dans votre fichierstrings.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
.
- Avant d'accéder à votre code Kotlin, mettez à jour la ressource de chaîne
order_details
dansstrings.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>
- Dans la classe
SummaryFragment
, mettez à jour votre méthodesendOrder()
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é quequantity
dans le ViewModel est de typeLiveData<Int>
, il est possible quesharedViewModel.quantity.value
ait une valeur nulle. Si la quantité est nulle, attribuez la valeur par défaut0
à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).
- Mettez ensuite en forme la chaîne
order_details
comme vous l'avez fait précédemment. Au lieu de transmettre directementnumberOfCupcakes
en tant qu'argument de quantité, créez la chaîne de cupcakes mise en forme avecresources.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)
}
}
- 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
etPickupFragment
. - 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
- Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une boîte de dialogue.
- 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.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- 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
- Lancez Android Studio.
- 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).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > New > Import Project (Fichier > Nouveau > Importer un projet).
- 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.
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.
- 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 dansapp: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 queIntent.EXTRA_EMAIL
,Intent.EXTRA_SUBJECT
etIntent.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 !