Projet : application Lunch Tray

1. Avant de commencer

Cet atelier de programmation présente une nouvelle application appelée Lunch Tray que vous allez créer vous-même. Il explique les différentes étapes à suivre pour mener à bien le projet d'application Lunch Tray, y compris sa configuration et ses tests dans Android Studio.

Cet atelier de programmation est différent des autres ateliers de ce cours. Contrairement aux précédents ateliers de programmation, celui-ci n'a pas pour objectif de vous fournir un tutoriel détaillé sur la création d'une application. Au lieu de cela, il vous aide à configurer un projet que vous devrez réaliser de manière indépendante, avec les instructions nécessaires pour finaliser une application et vérifier vous-même votre travail.

Au lieu du code de solution, nous proposons une suite de tests intégrés à l'application que vous téléchargerez. Vous exécuterez ces tests dans Android Studio (nous vous montrerons comment procéder plus tard dans cet atelier) et vérifierez si votre code passe ces tests avec succès. Plusieurs tentatives seront peut-être nécessaires. Même les développeurs professionnels ne réussissent pas tous les tests dès le premier essai. Une fois que votre code aura réussi tous les tests, vous pourrez considérer ce projet comme terminé.

Nous sommes conscients que vous pourriez préférer avoir directement accès à la solution. Toutefois, nous ne vous fournissons pas le code de la solution, car nous souhaitons que vous vous mettiez à la place d'un développeur professionnel. Peut-être devrez-vous renforcer des compétences différentes que vous ne maîtrisez pas encore, par exemple :

  • Apprendre des termes, messages d'erreur et extraits de code dans l'application que vous ne reconnaissez pas.
  • Tester le code, lire les erreurs, puis modifier le code et le tester à nouveau.
  • Réviser le contenu précédent sur les bases d'Android afin de rafraîchir vos connaissances.
  • Comparer le code dont vous savez qu'il fonctionne (code fourni dans le projet ou code de solution d'autres applications du module 3) avec le code que vous écrivez.

Cette nouvelle tâche peut sembler intimidante au premier abord, mais nous avons la certitude que si vous avez mené à bien le module 3, vous êtes fin prêt pour ce projet. Prenez votre temps et n'abandonnez pas. Faites-vous confiance !

Conditions préalables

  • Ce projet est destiné aux utilisateurs qui ont terminé le module 3 du cours sur les bases d'Android en Kotlin.

Objectifs de l'atelier

  • Vous utiliserez une application de commande de repas appelée "Lunch Tray", implémenterez un modèle de vue avec une liaison de données et ajouterez une navigation entre les fragments.

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio

2. Présentation de l'application terminée

Bienvenue dans le projet Lunch Tray !

Comme vous le savez probablement, la navigation est un élément fondamental du développement Android. Que vous utilisiez une application pour parcourir des recettes, trouver l'itinéraire vers votre restaurant préféré ou, surtout, commander des plats, il y a des chances que vous deviez parcourir plusieurs écrans de contenu. Dans ce projet, vous mettrez à profit les compétences que vous avez acquises dans le module 3 pour créer une application de commande de repas, appelée "Lunch Tray". Elle vous permettra d'implémenter un modèle de vue, des liaisons de données et la navigation entre les écrans.

Vous trouverez ci-dessous les captures d'écran finales de l'application. Lors du premier lancement de l'application Lunch Tray, l'utilisateur est invité à accéder à un écran comportant un seul bouton intitulé "Lancer la commande".

20fa769d4ba93ef3.png

Après avoir cliqué sur Lancer la commande, l'utilisateur peut sélectionner un participant parmi les options disponibles. Il peut modifier sa sélection, ce qui met à jour le sous-total indiqué en bas.

438b61180d690b3a.png

L'écran suivant permet à l'utilisateur d'ajouter un accompagnement.

768352680759d3e2.png

L'écran suivant permet à l'utilisateur de sélectionner un accompagnement pour sa commande.

8ee2bf41e9844614.png

Enfin, l'utilisateur voit un récapitulatif du coût de sa commande, qui comprend le sous-total, la taxe de vente et le coût total. Il peut également envoyer ou annuler la commande.

61c883c34d94b7f7.png

Dans les deux cas, l'utilisateur est redirigé vers le premier écran. Si l'utilisateur a envoyé la commande, un toast devrait s'afficher en bas de l'écran pour confirmer que cette opération a bien été effectuée.

acb7d7a5d9843bac.png

3. Premiers pas

Télécharger le code du projet

Notez que le nom du dossier est android-basics-kotlin-lunch-tray-app. Sélectionnez ce dossier lorsque vous ouvrez le projet dans Android Studio.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, 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 (Ouvrir).

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, 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) 8de56cba7583251f.png pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.

Avant de commencer à implémenter ViewModel et la navigation, prenez le temps de vérifier que le projet a bien été compilé et de vous familiariser avec celui-ci. Lorsque vous exécutez l'application pour la première fois, un écran vide s'affiche. MainActivity ne présente aucun fragment, car vous n'avez pas encore configuré le graphique de navigation.

La structure du projet doit être semblable à celle des autres projets sur lesquels vous avez travaillé. Des packages distincts sont fournis pour les données, le modèle et l'interface utilisateur, ainsi que des répertoires distincts pour les ressources.

a19fd8a4bc92f2fc.png

Toutes les options de déjeuner que l'utilisateur peut commander (plats principaux, accompagnements et garnitures) sont représentées par la classe MenuItem dans le package model. Les objets MenuItem ont un nom, une description, un prix et un type.

data class MenuItem(
    val name: String,
    val description: String,
    val price: Double,
    val type: Int
) {
    fun getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(price)
}

Le type est représenté par un entier provenant de l'objet ItemType dans le package constants.

object ItemType {
    val ENTREE = 1
    val SIDE_DISH = 2
    val ACCOMPANIMENT = 3
}

Les objets MenuItem individuels se trouvent dans DataSource.kt dans le package de données.

object DataSource {
    val menuItems = mapOf(
        "cauliflower" to
        MenuItem(
            name = "Cauliflower",
            description = "Whole cauliflower, brined, roasted, and deep fried",
            price = 7.00,
            type = ItemType.ENTREE
        ),
    ...
}

Cet objet contient simplement une carte composée d'une clé et d'un élément MenuItem correspondant. Vous accéderez à DataSource depuis ObjectViewModel, que vous implémenterez en premier.

Définir le modèle de vue

Comme illustré dans les captures d'écran de la page précédente, l'application demande à l'utilisateur trois choses : un plat, un accompagnement et une garniture. L'écran de récapitulatif de la commande affiche ensuite un sous-total et calcule la taxe de vente en fonction des articles sélectionnés, qui serviront à calculer le total de la commande.

Dans le package model, ouvrez OrderViewModel.kt. Vous verrez que quelques variables y sont déjà définies. La propriété menuItems vous permet simplement d'accéder à DataSource à partir de ViewModel.

val menuItems = DataSource.menuItems

Tout d'abord, il existe des variables pour previousEntreePrice, previousSidePrice et previousAccompanimentPrice. Étant donné que le sous-total est mis à jour au fur et à mesure que l'utilisateur fait son choix (plutôt que d'être ajouté à la fin), ces variables sont utilisées pour suivre la sélection précédente de l'utilisateur s'il la modifie avant de passer à l'écran suivant. Vous les utiliserez pour vérifier que le sous-total reflète la différence entre le prix des articles précédents et celui des articles actuellement sélectionnés.

private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0

Il existe également des variables privées, _entree, _side et _accompaniment, pour stocker l'article sélectionné. Elles sont de type MutableLiveData<MenuItem?>. Chacune d'elles est accompagnée d'une propriété de support publique, entree, side et accompaniment, de type immuable LiveData<MenuItem?>. Elles sont accessibles en fonction des mises en page des fragments pour afficher l'élément sélectionné à l'écran. La valeur MenuItem contenue dans l'objet LiveData peut également être nulle, car l'utilisateur peut ne pas sélectionner de plat principal, d'accompagnement, ni de garniture.

// Entree for the order
private val _entree = MutableLiveData<MenuItem?>()
val entree: LiveData<MenuItem?> = _entree

// Side for the order
private val _side = MutableLiveData<MenuItem?>()
val side: LiveData<MenuItem?> = _side

// Accompaniment for the order.
private val _accompaniment = MutableLiveData<MenuItem?>()
val accompaniment: LiveData<MenuItem?> = _accompaniment

Il existe également des variables LiveData pour le sous-total, le total et les taxes. Elles utilisent le format numérique afin d'être affichées sous la forme d'une devise.

// Subtotal for the order
private val _subtotal = MutableLiveData(0.0)
val subtotal: LiveData<String> = Transformations.map(_subtotal) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Total cost of the order
private val _total = MutableLiveData(0.0)
val total: LiveData<String> = Transformations.map(_total) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Tax for the order
private val _tax = MutableLiveData(0.0)
val tax: LiveData<String> = Transformations.map(_tax) {
    NumberFormat.getCurrencyInstance().format(it)
}

Enfin, le taux de taxe est une valeur codée en dur correspondant à 0,08 (8 %).

private val taxRate = 0.08

Vous devez implémenter six méthodes dans OrderViewModel.

setEntree(), setSide() et setAccompaniment()

Toutes ces méthodes devraient fonctionner de la même manière pour le plat principal, l'accompagnement et la garniture, respectivement. Par exemple, setEntree() doit fonctionner comme suit :

  1. Si _entree n'est pas null (c'est-à-dire si l'utilisateur a déjà sélectionné un plat, mais qu'il a modifié son choix), définissez previousEntreePrice sur le prix de current _entree.
  2. Si l'élément _subtotal n'est pas null, soustrayez previousEntreePrice du sous-total.
  3. Remplacez la valeur de _entree par le plat principal transmis à la fonction (accédez à MenuItem à l'aide de menuItems).
  4. Appelez updateSubtotal() en transmettant le prix du plat principal que vous venez de sélectionner.

La logique pour setSide() et setAccompaniment() est identique à l'implémentation de setEntree().

updateSubtotal()

updateSubtotal() est appelé avec un argument pour générer le nouveau prix qui doit être ajouté au sous-total. Cette méthode doit effectuer trois opérations :

  1. Si _subtotal n'est pas null, ajoutez itemPrice à _subtotal.
  2. Sinon, si _subtotal correspond à null, définissez _subtotal sur itemPrice.
  3. Une fois que _subtotal a été défini (ou mis à jour), appelez calculateTaxAndTotal() pour que ces valeurs soient mises à jour et reflètent le nouveau sous-total.

calculateTaxAndTotal()

calculateTaxAndTotal() doit mettre à jour les variables pour la taxe et le total en fonction du sous-total. Implémentez la méthode comme suit :

  1. Définissez la valeur de _tax sur le taux de taxe multiplié par le sous-total.
  2. Définissez _total sur le sous-total plus les taxes.

resetOrder()

resetOrder() est appelé lorsque l'utilisateur envoie ou annule une commande. Vous voulez vous assurer qu'il ne reste aucune donnée sur l'application lorsque l'utilisateur passe une nouvelle commande.

Pour implémenter resetOrder(), rétablissez toutes les variables que vous avez modifiées dans OrderViewModel sur leur valeur d'origine (0.0 ou null).

Créer des variables de liaison de données

Implémentez la liaison de données dans les fichiers de mise en page. Ouvrez les fichiers de mise en page, puis ajoutez des variables de liaison de données de type OrderViewModel et/ou la classe de fragment correspondante.

Vous devez implémenter tous les commentaires TODO pour définir les écouteurs de texte et de clics dans quatre fichiers de mise en page :

  1. fragment_entree_menu.xml
  2. fragment_side_menu.xml
  3. fragment_accompaniment_menu.xml
  4. fragment_checkout.xml

Chaque tâche est notée dans un commentaire TODO dans les fichiers de mise en page, mais les étapes sont résumées ci-dessous.

  1. Dans fragment_entree_menu.xml, dans la balise <data>, ajoutez une variable de liaison pour EntreeMenuFragment. Pour chacune des cases d'option, vous devez définir le plat principal dans la vie ViewModel lorsqu'elle est sélectionnée. Le texte du sous-total doit être mis à jour en conséquence. Vous devez également définir l'attribut onClick pour cancel_button et next_button de sorte à annuler la commande ou à passer à l'écran suivant.
  2. Procédez de la même manière dans fragment_side_menu.xml, en ajoutant une variable de liaison pour SideMenuFragment, sauf pour définir l'accompagnement dans le modèle de vue lorsque chaque case d'option est sélectionnée. Le texte du sous-total doit également être mis à jour, et vous devez aussi définir l'attribut onClick pour les boutons "Annuler" et "Suivant".
  3. Répétez l'opération, mais dans fragment_accompaniment_menu.xml, avec une variable de liaison pour AccompanimentMenuFragment, définissant la garniture lorsque chaque case d'option est sélectionnée. Là encore, vous devez également définir les attributs du texte du sous-total, des boutons "Annuler" et "Suivant".
  4. Dans fragment_checkout.xml, vous devez ajouter une balise <data> pour pouvoir définir des variables de liaison. Dans la balise <data>, ajoutez deux variables de liaison, l'une pour OrderViewModel et l'autre pour CheckoutFragment. Dans les vues de texte, vous devez définir le nom et le prix du plat principal, de l'accompagnement et de la garniture sélectionnés à partir de la vue OrderViewModel. Vous devez également définir le sous-total, les taxes et le total à partir de la vue OrderViewModel. Ensuite, définissez onClickAttributes pour le moment où la commande est envoyée, ainsi que pour le moment où la commande est annulée, à l'aide des fonctions appropriées issues de CheckoutFragment.

.

Initialiser les variables de liaison de données dans les fragments

Initialisez les variables de liaison de données dans les fichiers de fragment correspondants au sein de la méthode onViewCreated().

  1. EntreeMenuFragment
  2. SideMenuFragment
  3. AccompanimentMenuFragment
  4. CheckoutFragment

Créer le graphique de navigation

Comme vous l'avez vu dans le module 3, un graphique de navigation est hébergé dans une vue FragmentContainerView, contenue dans une activité. Ouvrez activity_main.xml et remplacez TODO par le code suivant pour déclarer une vue FragmentContainerView.

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/nav_host_fragment"
   android:name="androidx.navigation.fragment.NavHostFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:defaultNavHost="true"
   app:navGraph="@navigation/mobile_navigation"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintLeft_toLeftOf="parent"
   app:layout_constraintRight_toRightOf="parent"
   app:layout_constraintTop_toTopOf="parent" />

Le graphique de navigation mobile_navigation.xml se trouve dans le package res.navigation.

e3381215c35c1726.png

Il s'agit du graphique de navigation de l'application. Toutefois, le fichier est actuellement vide. Votre tâche consiste à ajouter des destinations au graphique de navigation et à modéliser la navigation suivante entre les écrans.

  1. Navigation de StartOrderFragment vers EntreeMenuFragment
  2. Navigation de EntreeMenuFragment vers SideMenuFragment
  3. Navigation de SideMenuFragment vers AccompanimentMenuFragment
  4. Navigation de AccompanimentMenuFragment vers CheckoutFragment
  5. Navigation de CheckoutFragment vers StartOrderFragment
  6. Navigation de EntreeMenuFragment vers StartOrderFragment
  7. Navigation de SideMenuFragment vers StartOrderFragment
  8. Navigation de AccompanimentMenuFragment vers StartOrderFragment
  9. La destination de départ doit correspondre à StartOrderFragment

Une fois que vous avez configuré le graphique de navigation, vous devez effectuer la navigation dans les classes de fragment. Implémentez les commentaires TODO restants dans les fragments, ainsi que dans MainActivity.kt.

  1. Pour la méthode goToNextScreen() dans EntreeMenuFragment, SideMenuFragment et AccompanimentMenuFragment, accédez à l'écran suivant dans l'application.
  2. Pour la méthode cancelOrder() dans EntreeMenuFragment, SideMenuFragment, AccompanimentMenuFragment et CheckoutFragment, appelez d'abord resetOrder() au niveau de la vue sharedViewModel, puis accédez à StartOrderFragment.
  3. Dans StartOrderFragment, implémentez setOnClickListener() pour accéder à EntreeMenuFragment.
  4. Dans CheckoutFragment, implémentez la méthode submitOrder(). Appelez resetOrder() au niveau de la vue sharedViewModel, puis accédez à StartOrderFragment.
  5. Enfin, dans MainActivity.kt, définissez navController sur navController à partir de NavHostFragment.

4. Tester votre application

Le projet "Lunch Tray" contient une cible "androidTest" avec plusieurs scénarios de test : MenuContentTests, NavigationTests et OrderFunctionalityTests.

Exécuter vos tests

Pour exécuter vos tests, vous pouvez effectuer l'une des opérations suivantes :

Pour un scénario de test unique, ouvrez une classe de scénario de test et cliquez sur la flèche verte à gauche de la déclaration de classe. Vous pouvez ensuite sélectionner l'option "Run" (Exécuter) dans le menu. Tous les tests seront exécutés dans le scénario de test.

8ddcbafb8ec14f9b.png

Souvent, il suffit d'exécuter un seul test, par exemple lorsqu'un test a échoué et que tous les autres ont réussi. Il est possible d'exécuter un seul test de la même manière que vous le feriez pour un scénario de test complet. Utilisez la flèche verte et sélectionnez l'option Exécuter.

335664b7fc8b4fb5.png

Si vous avez plusieurs scénarios de test, vous pouvez également exécuter l'ensemble de la suite de tests. Comme pour l'exécution de l'application, cette option se trouve dans le menu Exécuter.

80312efedf6e4dd3.png

Notez qu'Android Studio utilise par défaut la dernière cible que vous avez exécutée (applications, cibles de test, etc.). Par conséquent, si le menu indique toujours Run > Run 'app' (Exécuter > Exécuter 'application'), vous pouvez exécuter la cible de test en sélectionnant Run > Run (Exécuter > Exécuter).

95aacc8f749dee8e.png

Sélectionnez ensuite la cible de test dans le menu pop-up.

8b702efbd4d21d3d.png

5. Facultatif : faites-nous part de vos commentaires

Nous aimerions connaître votre avis sur ce projet. Répondez à cette courte enquête. Vos commentaires nous aideront à mener à bien les prochains projets de ce cours.