1. Avant de commencer
Vous avez appris à utiliser les activités, les fragments, les intents, la liaison de données, les composants de navigation et les principes de base des composants d'architecture. Dans cet atelier de programmation, vous allez combiner tous ces apprentissages et travailler sur un exemple avancé : une application de commande de cupcakes.
Vous allez apprendre à utiliser un ViewModel
partagé pour partager des données entre les fragments d'une même activité, et de nouveaux concepts comme les transformations LiveData
.
Conditions préalables
- Vous maîtrisez les mises en page Android au format XML.
- Vous maîtrisez les principes de base du composant Navigation dans Jetpack.
- Vous êtes capable de créer un graphique de navigation avec des destinations de fragment dans une application.
- Vous avez déjà utilisé des fragments dans une activité.
- Vous savez créer un
ViewModel
pour stocker les données de l'application. - Vous savez utiliser la liaison de données avec
LiveData
pour synchroniser l'interface utilisateur avec les données de l'application dans leViewModel
.
Points abordés
- Mettre en œuvre les pratiques recommandées pour l'architecture des applications dans un cas d'utilisation plus avancé.
- Utiliser une propriété
ViewModel
partagée entre les fragments d'une activité. - Appliquer une transformation
LiveData
.
Objectifs de l'atelier
- L'application Cupcake, qui affiche un flux de commande de gâteaux et permet à l'utilisateur de choisir le goût, la quantité et la date de retrait de la commande.
Ce dont vous avez besoin
- Un ordinateur sur lequel est installé Android Studio
- Code de démarrage de l'application Cupcake.
2. Présentation de l'application de démarrage
Présentation de l'application Cupcake
L'application cupcake vous montre comment concevoir et implémenter une application de commande en ligne. À la fin de ce parcours, vous obtiendrez une application Cupcake composée des écrans suivants. L'utilisateur peut choisir la quantité, le goût et d'autres options lors de la commande.
Télécharger le code de démarrage pour cet atelier de programmation
Cet atelier de programmation fournit un code de démarrage que vous pouvez étendre avec les fonctionnalités qui y sont enseignées. Le code de démarrage contient du code que vous avez déjà vu dans les ateliers de programmation précédents.
Si vous téléchargez le code de démarrage depuis GitHub, le nom du dossier du projet est android-basics-kotlin-cupcake-app-starter
. Sélectionnez ce dossier lorsque vous ouvrirez 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.
Explication du code de démarrage
- Ouvrez le projet dans Android Studio. Le nom du dossier du projet est
android-basics-kotlin-cupcake-app-starter
. Ensuite, exécutez l'application. - Parcourez les fichiers pour comprendre le code de démarrage. Pour les fichiers de mise en page, vous pouvez utiliser l'option Split (Diviser) disponible dans le coin supérieur droit pour afficher un aperçu de la mise en page et du fichier XML en même temps.
- En compilant et en exécutant l'application, vous remarquerez qu'elle est incomplète. Les boutons ne font rien de spécial (sauf afficher un message
Toast
) et vous ne pouvez pas naviguer vers les autres fragments.
Voici une présentation des fichiers importants du projet.
MainActivity :
Le code MainActivity
est semblable au code généré par défaut, qui définit la vue du contenu de l'activité sur activity_main.xml
. Ce code utilise un constructeur AppCompatActivity(@LayoutRes int contentLayoutId)
paramétré qui accepte une mise en page qui sera gonflée dans le cadre de super.onCreate(savedInstanceState)
.
Le code dans la classe MainActivity
class MainActivity : AppCompatActivity(R.layout.activity_main)
est identique au code suivant qui utilise le constructeur AppCompatActivity
par défaut :
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Mises en page (dossier res/layout) :
Le dossier de ressources layout
contient des fichiers de mise en page d'activité et de fragment. Il s'agit de fichiers de mise en page simples, et le format XML a déjà été utilisé lors des précédents ateliers de programmation.
fragment_start.xml
est le premier écran qui s'affiche dans l'application. Il contient une image représentant un cupcake et trois boutons permettant de choisir le nombre de cupcakes à commander : un, six et douze cupcakes.fragment_flavor.xml
affiche une liste de types de cupcakes sous la forme de cases d'option, et un bouton Next (Suivant).fragment_pickup.xml
permet de sélectionner le jour du retrait de la commande, le bouton Suivant d'accéder à l'écran récapitulatif.fragment_summary.xml
affiche un récapitulatif des détails de la commande, y compris la quantité, le type de produit et un bouton permettant d'envoyer la commande vers une autre application.
Classes de fragments :
StartFragment.kt
est le premier écran qui s'affiche dans l'application. Cette classe contient le composant View Binding et un gestionnaire de clics pour les trois boutons.- Les classes
FlavorFragment.kt
,PickupFragment.kt
etSummaryFragment.kt
contiennent du code récurrent et un gestionnaire de clics pour les boutons Next (Suivant) ou Send Order to Another App (Envoyer la commande vers une autre application). Ces deux éléments affichent un message toast.
Ressources (dossier res) :
- Le dossier
drawable
contient l'élément cupcake pour le premier écran, ainsi que les fichiers de l'icône de lanceur. navigation/nav_graph.xml
contient quatre destinations de fragment (startFragment
,flavorFragment
,pickupFragment
etsummaryFragment
), sans actions, que vous définirez ultérieurement dans l'atelier.- Le dossier
values
contient les couleurs, les dimensions, les chaînes, les styles et les thèmes utilisés pour personnaliser le thème de l'application. Ces types de ressources devraient vous être familiers suite aux précédents ateliers.
3. Compléter le graphique de navigation
Dans cette tâche, vous allez connecter les écrans de l'application Cupcake et terminer l'implémentation de la navigation dans l'application.
Vous souvenez-vous de ce dont nous avons besoin pour utiliser le composant Navigation ? Suivez ce guide pour obtenir un rappel sur différents points pour votre projet et votre application, de façon à :
- inclure la bibliothèque de navigation Jetpack ;
- ajouter un
NavHost
à l'activité ; - créer un graphique de navigation ;
- ajouter des destinations de fragment au graphique de navigation.
Connecter des destinations dans le graphique de navigation
- Dans Android Studio, dans la fenêtre Project (Projet), accédez au fichier res > navigation > nav_graph.xml. Accédez à l'onglet Design (Conception) s'il n'est pas déjà sélectionné.
- L'éditeur de navigation s'ouvre : il affiche le graphique de navigation dans votre application, et les quatre fragments qui existent déjà dans l'application.
- Connectez les destinations de fragment dans le graphique de navigation. Créez une action de
startFragment
àflavorFragment
, une connexion deflavorFragment
àpickupFragment
, ainsi qu'une connexion depickupFragment
àsummaryFragment
. Suivez ces étapes si vous avez besoin d'instructions plus détaillées. - Pointez sur startFragment jusqu'à ce que la bordure grise entoure le fragment et que le cercle gris apparaisse au centre du bord droit du fragment. Cliquez sur le cercle et faites-le glisser vers flavorFragment, puis relâchez le bouton de la souris.
- La flèche entre les deux fragments indique une connexion réussie, ce qui signifie que vous pourrez naviguer du startFragment au flavorFragment. C'est ce qu'on appelle une action de navigation, une notion déjà abordée lors d'un précédent atelier.
- De la même façon, ajoutez les actions de navigation de flavorFragment à pickupFragment, et de pickupFragment à summaryFragment. Une fois ces actions de navigation créées, le graphique de navigation terminé devrait se présenter comme suit.
- Les trois actions que vous avez créées doivent également apparaître dans l'arborescence des composants.
- Lorsque vous définissez un graphique de navigation, vous devez également spécifier la destination de départ. Comme vous pouvez le voir ici, une icône représentant une maison apparaît à côté de startFragment.
Cela indique que startFragment sera le premier fragment à être affiché dans NavHost
. Conservez le comportement souhaité pour notre application. Pour référence ultérieure, vous pouvez toujours modifier la destination de départ en effectuant un clic droit sur un fragment, puis en sélectionnant l'option de menu Set as Start Destination (Définir comme destination de départ).
Passer du fragment "start" au fragment "flavor"
Vous allez ensuite ajouter du code pour passer de startFragment à flavorFragment en appuyant sur les boutons du premier fragment, au lieu d'afficher un message Toast
. Vous trouverez ci-dessous la référence à la mise en page de l'élément startFragment. Vous transmettrez la quantité de cupcakes à l'élément flavorFragment lors d'une prochaine tâche.
- Dans la fenêtre Projet, ouvrez le fichier Kotlin app > java > com.example.cupcake > StartFragment.
- Dans la méthode
onViewCreated()
, notez que les écouteurs de clics sont définis sur les trois boutons. Lorsque vous appuyez sur chaque bouton, la méthodeorderCupcake()
est appelée avec le nombre de cupcakes (1, 6 ou 12 cupcakes), qui est son paramètre.
Code de référence :
orderOneCupcake.setOnClickListener { orderCupcake(1) }
orderSixCupcakes.setOnClickListener { orderCupcake(6) }
orderTwelveCupcakes.setOnClickListener { orderCupcake(12) }
- Dans la méthode
orderCupcake()
, remplacez le code qui affiche le message de toast par le code permettant d'accéder au fragment "flavor". Obtenez leNavController
utilisant la méthodefindNavController()
et appeleznavigate()
en lui transmettant l'ID de l'action,R.id.action_startFragment_to_flavorFragment
. Assurez-vous que cet ID d'action correspond à l'action déclarée dans votrenav_graph.xml.
.
Remplacer
fun orderCupcake(quantity: Int) {
Toast.makeText(activity, "Ordered $quantity cupcake(s)", Toast.LENGTH_SHORT).show()
}
avec
fun orderCupcake(quantity: Int) {
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Ajoutez le fichier
import
d'importationandroidx.navigation.fragment.findNavController
ou faites votre choix parmi les options fournies par Android Studio.
Ajouter la navigation aux fragments "flavor" et "pickup"
Comme pour la tâche précédente, vous allez ajouter la navigation aux autres fragments : "flavor" et "pickup".
- Ouvrez app > java > com.example.cupcake > FlavorFragment.kt. La méthode appelée dans l'écouteur de clics du bouton Suivant est
goToNextScreen()
. - Dans
FlavorFragment.kt
, dans la méthodegoToNextScreen()
, remplacez le code affichant le toast pour naviguer vers l'élément pickupFragment. Utilisez l'ID d'actionR.id.action_flavorFragment_to_pickupFragment
et assurez-vous qu'il correspond à l'action déclarée dans votre fichiernav_graph.xml.
.
fun goToNextScreen() {
findNavController().navigate(R.id.action_flavorFragment_to_pickupFragment)
}
Pensez à importer l'élément import androidx.navigation.fragment.findNavController
.
- De même, dans
PickupFragment.kt
, dans la méthodegoToNextScreen()
, remplacez le code existant pour accéder à l'élément summaryFragment.
fun goToNextScreen() {
findNavController().navigate(R.id.action_pickupFragment_to_summaryFragment)
}
Importez androidx.navigation.fragment.findNavController
.
- Exécutez l'application. Vérifiez que les boutons fonctionnent bien pour naviguer d'un écran à l'autre. Les informations affichées sur chaque fragment peuvent être incomplètes, mais ne vous inquiétez pas, les bonnes données y seront insérées lors des prochaines étapes.
Modifier le titre dans la barre d'application
Lorsque vous naviguez dans l'application, son titre s'affiche dans la barre d'application. Il s'affiche toujours comme suit : Cupcake.
Il serait judicieux de fournir un titre plus pertinent pour la fonctionnalité du fragment en cours d'utilisation.
Dans la barre d'application (également appelée barre d'action), modifiez le titre de chaque fragment à l'aide du NavController
et affichez le bouton Haut (←).
- Dans
MainActivity.kt
, remplacez la méthodeonCreate()
pour configurer le contrôleur de navigation. Obtenez une instance deNavController
à partir deNavHostFragment
. - Appelez
setupActionBarWithNavController(navController)
en transmettantNavController
dans l'instance. Cette option permet d'afficher un titre dans la barre d'application en fonction du libellé de la destination, et d'afficher le bouton Haut lorsque vous ne vous trouvez pas sur une destination de niveau supérieur.
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
setupActionBarWithNavController(navController)
}
}
- Ajoutez les importations nécessaires lorsque Android Studio vous le demande.
import android.os.Bundle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
- Définissez les titres de la barre d'application pour chaque fragment. Ouvrez
navigation/nav_graph.xml
et passez à l'onglet Code. - Dans
nav_graph.xml
, modifiez l'attributandroid:label
pour chaque destination de fragment. Utilisez les ressources de chaîne suivantes, qui ont déjà été déclarées dans l'application de démarrage.
Pour l'élément startFragment, utilisez @string/app_name
avec la valeur Cupcake
.
Pour l'élément flavorFragment, utilisez @string/choose_flavor
avec la valeur Choose Flavor
.
Pour l'élément pickupFragment, utilisez @string/choose_pickup_date
avec la valeur Choose Pickup Date
.
Pour le fragment order_summary, utilisez @string/order_summary
avec la valeur Order Summary
.
<navigation ...>
<fragment
android:id="@+id/startFragment"
...
android:label="@string/app_name" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/flavorFragment"
...
android:label="@string/choose_flavor" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/pickupFragment"
...
android:label="@string/choose_pickup_date" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/summaryFragment"
...
android:label="@string/order_summary" ... />
</navigation>
- Exécutez l'application. Notez que le titre dans la barre d'application change lorsque vous naviguez d'une destination de fragment à l'autre. Notez également que le bouton Haut (flèche ←) s'affiche désormais dans la barre d'application. Si vous appuyez dessus, il ne se passe rien. Vous implémenterez le comportement du bouton Haut dans le prochain atelier de programmation.
4. Créer un ViewModel partagé
Passons à la saisie des données correctes dans chacun des fragments. Vous allez utiliser un ViewModel
partagé pour enregistrer les données de l'application dans un seul ViewModel
. Plusieurs fragments de l'application accéderont au ViewModel
partagé en fonction du champ d'application de leur activité.
Dans la plupart des applications de production, il est courant de partager des données entre les fragments. Par exemple, dans la version finale de l'application Cupcake (pour cet atelier, voir les captures d'écran ci-dessous), l'utilisateur sélectionne la quantité de cupcakes dans le premier écran. Dans le second, le prix est calculé et affiché en fonction de la quantité saisie. De même, d'autres données de l'application, telles que la saveur et la date de retrait, sont également utilisées dans l'écran récapitulatif.
En examinant les caractéristiques de l'application, il peut sembler utile de stocker ces informations de commande dans un seul ViewModel
, qui peut être partagé entre les différents fragments de cette activité. N'oubliez pas que ViewModel
fait partie des composants d'architecture Android. Les données de l'application enregistrées dans ViewModel
sont conservées lors des modifications de configuration. Pour ajouter un ViewModel
à votre application, vous devez créer une classe basée sur la classe ViewModel
.
Créer un OrderViewModel
Dans cette tâche, vous allez créer un ViewModel
partagé pour l'application Cupcake, appelé OrderViewModel
. Vous allez également ajouter les données de l'application en tant que propriétés dans ViewModel
, ainsi que des méthodes pour mettre à jour et modifier les données. Voici les propriétés de la classe :
- Quantité de la commande (
Integer
) - Saveur du cupcake (
String
) - Date de retrait (
String
) - Prix (
Double
)
Suivre les bonnes pratiques de ViewModel
Dans un ViewModel
, il est recommandé de ne pas exposer les données du ViewModel en tant que variables public
. Sinon, les données des applications peuvent être modifiées de manière inattendue par les classes externes et créer des cas particuliers que votre application ne s'attendait pas à gérer. Définissez plutôt ces propriétés modifiables private
, implémentez une propriété de sauvegarde et exposez une version immuable public
de chaque propriété, si nécessaire. La convention consiste à ajouter un trait de soulignement (_
) au nom des propriétés modifiables private
.
Voici les méthodes permettant de mettre à jour les propriétés ci-dessus, selon le choix de l'utilisateur :
setQuantity(numberCupcakes: Int)
setFlavor(desiredFlavor: String)
setDate(pickupDate: String)
Vous n'avez pas besoin d'une méthode "setter" pour le prix, car vous le calculerez dans la OrderViewModel
à l'aide d'autres propriétés. Les étapes ci-dessous vous expliquent comment implémenter le ViewModel
partagé.
Dans votre projet, vous allez créer un package appelé model
et ajouter la classe OrderViewModel
. Le code du ViewModel sera ainsi séparé du reste du code de l'interface utilisateur (fragments et activités). Il est recommandé de diviser le code en packages selon la fonctionnalité.
- Dans la fenêtre Projet d'Android Studio, effectuez un clic droit sur com.example.cupcake > Nouveau > Package.
- Une boîte de dialogue Nouveau package s'ouvre. Attribuez le nom
com.example.cupcake.model
au package.
- Créez la classe Kotlin
OrderViewModel
sous le packagemodel
. Dans la fenêtre Projet, effectuez un clic droit sur le packagemodel
, puis sélectionnez Nouveau > Fichier/Classe Kotlin. Dans la boîte de dialogue qui s'ouvre, nommez le fichierOrderViewModel
.
- Dans
OrderViewModel.kt
, modifiez la signature de la classe pour qu'elle se base sur leViewModel
.
import androidx.lifecycle.ViewModel
class OrderViewModel : ViewModel() {
}
- Dans la classe
OrderViewModel
, ajoutez les propriétés décrites ci-dessus en tant que propriétéprivate
val
. - Définissez les types de propriétés sur
LiveData
et ajoutez des champs de stockages aux propriétés, afin qu'elles puissent être observables et que l'UI puisse être mise à jour lorsque les données sources du ViewModel sont modifiées.
private val _quantity = MutableLiveData<Int>(0)
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>("")
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>("")
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>(0.0)
val price: LiveData<Double> = _price
Vous devez importer les classes suivantes :
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
- Dans la classe
OrderViewModel
, ajoutez les méthodes décrites ci-dessus. Dans les méthodes, attribuez l'argument transmis aux propriétés modifiables. - Étant donné que ces méthodes Setter doivent être appelées depuis l'extérieur du ViewModel, conservez-les en tant que méthodes
public
. Cela signifie qu'aucunprivate
ni autre modificateur de visibilité n'est requis avant le mot cléfun
. En Kotlin, le modificateur de visibilité par défaut estpublic
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
}
fun setFlavor(desiredFlavor: String) {
_flavor.value = desiredFlavor
}
fun setDate(pickupDate: String) {
_date.value = pickupDate
}
- Créez et exécutez votre application pour vous assurer qu'elle ne présente aucune erreur de compilation. L'interface ne devrait pas encore être modifiée.
Bien joué ! Vous êtes maintenant prêt à créer votre ViewModel. Vous ajouterez progressivement des éléments à cette classe au fil du développement des fonctionnalités de votre application, et lorsque vous constaterez le besoin de recourir à d'autres propriétés et méthodes dans votre classe.
Si le nom des classes, des propriétés ou des méthodes s'affiche en gris dans Android Studio, c'est normal. Cela signifie que la classe, les propriétés ou les méthodes ne sont pas utilisées pour le moment. Pour le moment seulement ! C'est la prochaine étape.
5. Utiliser ViewModel pour mettre à jour l'UI
Dans cette tâche, vous allez utiliser le ViewModel partagé que vous avez créé pour mettre à jour l'interface utilisateur de l'application. La principale différence dans l'implémentation d'un ViewModel partagé réside dans la façon dont nous y accédons depuis les contrôleurs d'UI. Vous allez utiliser l'instance d'activité à la place de l'instance du fragment. Nous vous expliquerons comment procéder dans les sections suivantes.
Le ViewModel peut donc être partagé entre plusieurs fragments. Chaque fragment peut accéder au ViewModel pour vérifier certains détails de la commande ou mettre à jour certaines données du ViewModel.
Mettre à jour StartFragment pour utiliser le ViewModel
Pour utiliser le ViewModel partagé dans StartFragment
, initialisez OrderViewModel
à l'aide de activityViewModels()
au lieu de la classe déléguée viewModels()
.
viewModels()
vous donne l'instanceViewModel
limitée au fragment actuel. La procédure diffère selon les fragments.activityViewModels()
vous donne l'instanceViewModel
limitée à l'activité actuelle. Par conséquent, l'instance reste la même sur plusieurs fragments de la même activité.
Utiliser la délégation de propriété Kotlin
En Kotlin, chaque propriété modifiable (var
) dispose automatiquement de fonctions "getter" et "setter". Ces fonctions sont appelées lorsque vous attribuez une valeur à la propriété ou la lisez. (Pour une propriété en lecture seule (val
), seule la fonction "getter" est générée par défaut. Cette fonction getter est appelée lorsque vous lisez la valeur d'une propriété en lecture seule.)
La délégation de propriété en Kotlin vous aide à transférer la responsabilité "getter-setter" à une autre classe.
Cette classe (appelée classe déléguée) fournit les fonctions getter et setter de la propriété et gère ses modifications.
Un délégué de propriété est défini à l'aide de la clause by
et d'une instance de classe déléguée :
// Syntax for property delegation
var <property-name> : <property-type> by <delegate-class>()
- Dans la classe
StartFragment
, obtenez une référence au ViewModel partagé en tant que variable de classe. Utilisez le délégué de propriété Kotlinby activityViewModels()
de la bibliothèquefragment-ktx
.
private val sharedViewModel: OrderViewModel by activityViewModels()
Vous aurez peut-être besoin de ces nouvelles importations :
import androidx.fragment.app.activityViewModels
import com.example.cupcake.model.OrderViewModel
- Répétez l'étape ci-dessus pour les classes
FlavorFragment
,PickupFragment
etSummaryFragment
. Vous utiliserez cette instancesharedViewModel
dans les sections suivantes de l'atelier de programmation. - Revenez à la classe
StartFragment
. Vous pouvez maintenant utiliser le ViewModel. Au début de la méthodeorderCupcake()
, appelez la méthodesetQuantity()
dans le ViewModel partagé pour mettre à jour la quantité, avant d'accéder au flavorFragment.
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Dans la classe
OrderViewModel
, ajoutez la méthode suivante pour vérifier si la saveur du produit commandé a été définie ou non. Vous utiliserez cette méthode dans la classeStartFragment
lors des prochaines tâches.
fun hasNoFlavorSet(): Boolean {
return _flavor.value.isNullOrEmpty()
}
- Dans la classe
StartFragment
, dans la méthodeorderCupcake()
, après avoir défini la quantité, définissez "Vanille" comme saveur par défaut si aucun parfum n'est défini, avant d'accéder au flavorFragment. Une fois la méthode terminée, elle doit se présenter comme suit :
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
if (sharedViewModel.hasNoFlavorSet()) {
sharedViewModel.setFlavor(getString(R.string.vanilla))
}
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Créez votre application pour vous assurer qu'elle ne présente aucune erreur de compilation. L'interface ne devrait pas être modifiée.
6. Utiliser ViewModel avec la liaison de données
Vous allez ensuite utiliser la liaison de données pour lier les données du ViewModel à l'interface utilisateur. Vous mettrez également à jour le ViewModel partagé en fonction des sélections effectuées par l'utilisateur dans l'interface utilisateur.
Rappel sur la liaison de données
Rappelez-vous que la Bibliothèque de liaison de données fait partie d'Android Jetpack. La liaison de données lie les composants d'interface utilisateur dans vos mises en page aux sources de données de votre application à l'aide d'un format déclaratif. Pour faire simple, la liaison de données consiste à lier les données (à partir du code) aux vues et à lier les affichages (liaison d'affichages au code). En configurant ces liaisons et en rendant les mises à jour automatiques, vous réduisez le risque d'erreurs si vous oubliez de mettre à jour manuellement l'UI de votre code.
Modifier le type de produit selon le choix de l'utilisateur
- Dans
layout/fragment_flavor.xml
, ajoutez une balise<data>
dans la balise racine<layout>
. Ajoutez une variable de mise en page nomméeviewModel
de typecom.example.cupcake.model.OrderViewModel
. Assurez-vous que le nom du package dans l'attribut de type correspond au nom du package de la classe du ViewModel partagé,OrderViewModel
, dans votre application.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- De même, répétez l'étape ci-dessus pour
fragment_pickup.xml
etfragment_summary.xml
pour ajouter la variable de mise en pageviewModel
. Vous utiliserez cette variable dans les sections suivantes. Vous n'avez pas besoin d'ajouter ce code dansfragment_start.xml
, car cette mise en page n'utilise pas le ViewModel partagé. - Dans la classe
FlavorFragment
, dansonViewCreated()
, liez l'instance du ViewModel à l'instance du ViewModel partagé de la mise en page. Ajoutez le code suivant dans le blocbinding?.
apply
.
binding?.apply {
viewModel = sharedViewModel
...
}
Appliquer la fonction Scope
C'est la première fois que vous rencontrez la fonction apply
en langage Kotlin. apply
est une fonction de champ d'application appartenant à la bibliothèque standard Kotlin. Elle exécute un bloc de code dans le contexte d'un objet. Il s'agit d'un champ d'application temporaire qui vous permet d'accéder à l'objet sans son nom. Le principal cas d'utilisation de apply
est la configuration d'un objet. Ces appels peuvent être lus comme suit : "appliquer les attributions suivantes à l'objet".
Exemple :
clark.apply {
firstName = "Clark"
lastName = "James"
age = 18
}
// The equivalent code without apply scope function would look like the following.
clark.firstName = "Clark"
clark.lastName = "James"
clark.age = 18
- Répétez la même étape pour la méthode
onViewCreated()
dans les classesPickupFragment
etSummaryFragment
.
binding?.apply {
viewModel = sharedViewModel
...
}
- Dans
fragment_flavor.xml
, utilisez la nouvelle variable de mise en pageviewModel
pour définir l'attributchecked
des cases d'option en fonction de la valeurflavor
du ViewModel. Si la saveur représentée par une case d'option est identique à celle enregistrée dans le ViewModel, affichez la case d'option sélectionnée (checked
= true). L'expression de liaison de l'état coché du curseurRadioButton
Vanille se présente comme suit :
@{viewModel.flavor.equals(@string/vanilla)}
Globalement, vous comparez la propriété viewModel.flavor
à la ressource de chaîne correspondante à l'aide de la fonction equals
pour déterminer si l'état coché doit être vrai ou faux.
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:checked="@{viewModel.flavor.equals(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:checked="@{viewModel.flavor.equals(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:checked="@{viewModel.flavor.equals(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:checked="@{viewModel.flavor.equals(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:checked="@{viewModel.flavor.equals(@string/coffee)}"
.../>
</RadioGroup>
Expressions "listener binding"
Les expressions "listener binding" sont des expressions lambda qui s'exécutent lorsqu'un événement se produit, comme lors d'un événement onClick
. Elles sont semblables aux références de méthodes telles que textview.setOnClickListener(clickListener)
, mais elles vous permettent d'exécuter des expressions de liaison de données arbitraires.
- Dans
fragment_flavor.xml
, ajoutez des écouteurs d'événements aux cases d'option à l'aide des expressions "listener binding". Utilisez une expression lambda sans paramètre et appelezviewModel
.setFlavor()
en transmettant la ressource de chaîne de saveur correspondante.
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:onClick="@{() -> viewModel.setFlavor(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:onClick="@{() -> viewModel.setFlavor(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:onClick="@{() -> viewModel.setFlavor(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:onClick="@{() -> viewModel.setFlavor(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:onClick="@{() -> viewModel.setFlavor(@string/coffee)}"
.../>
</RadioGroup>
- Exécutez l'application. Notez que l'option Vanilla est sélectionnée par défaut dans le fragment.
Parfait ! Vous pouvez maintenant passer aux fragments suivants.
7. Mettre à jour le fragment "pickup" et "summary" pour utiliser le ViewModel
Parcourez l'application et notez que dans le pickupFragment, les libellés des cases d'option sont vides. Dans cette tâche, vous allez calculer les quatre dates de retrait disponibles et les afficher dans le pickupFragment. Il existe plusieurs façons d'afficher une date mise en forme. Voici quelques utilitaires fournis par Android pour vous aider à effectuer cette opération.
Créer une liste d'options de retrait
Outil de mise en forme des dates
Le framework Android fournit une classe appelée SimpleDateFormat
, qui permet de mettre en forme et d'analyser les dates en tenant compte des paramètres régionaux. Elle permet de mettre en forme (date → texte) et d'analyser les dates (texte → date).
Vous pouvez créer une instance de SimpleDateFormat
en transmettant une chaîne de modèle et un paramètre régional :
SimpleDateFormat("E MMM d", Locale.getDefault())
Une chaîne de format comme "E MMM d"
est une représentation des formats Date et Heure. Les lettres de 'A'
à 'Z'
et de 'a'
à 'z'
sont interprétées comme des lettres représentant des composants d'une chaîne de date ou d'heure. Par exemple, d
représente le jour du mois, y
l'année et M
le mois. Si la date est le 4 janvier 2018, la chaîne de modèle "EEE, MMM d"
analyse "Wed, Jul 4"
. Pour obtenir la liste complète des modèles, consultez la documentation.
Un objet Locale
représente une région géographique, politique ou culturelle spécifique. Il représente une combinaison de langue, pays et variante. Les paramètres régionaux permettent de modifier la présentation des informations (comme les nombres ou les dates) pour suivre les différentes conventions régionales. La date et l'heure sont sensibles aux paramètres régionaux, car ils s'écrivent différemment selon les régions du monde. Vous allez utiliser la méthode Locale.getDefault()
pour récupérer les informations sur les paramètres régionaux définis sur l'appareil de l'utilisateur et les transmettre au constructeur SimpleDateFormat
.
Les paramètres régionaux dans Android sont composés de la langue et du code pays. Il s'agit de codes ISO à deux lettres en minuscules, comme "en" pour l'anglais. Les codes pays sont des codes pays ISO à deux lettres en majuscules, comme "US" pour les États-Unis.
Utilisez maintenant SimpleDateFormat
et Locale
pour déterminer les dates de retrait disponibles pour l'application Cupcake.
- Dans la classe
OrderViewModel
, ajoutez la fonctiongetPickupOptions()
ci-dessous pour créer et renvoyer la liste des dates de retrait. Dans la méthode, créez une variableval
appeléeoptions
et initialisez-la surmutableListOf
<String>()
.
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
}
- Créez une chaîne de mise en forme à l'aide de
SimpleDateFormat
en transmettant la chaîne de modèle"E MMM d"
et les paramètres régionaux. Dans la chaîne du format,E
correspond au jour de la semaine et analyse la requête mardi 10 décembre.
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
Importez java.text.SimpleDateFormat
et java.util.Locale
lorsqu'Android Studio vous le demande.
- Obtenez une instance
Calendar
et affectez-la à une nouvelle variable. Définissez-la commeval
. Cette variable contiendra la date et l'heure actuelles. Importez égalementjava.util.Calendar
.
val calendar = Calendar.getInstance()
- Créez une liste de dates en commençant par la date du jour et les trois dates suivantes. Comme vous aurez besoin de quatre options de date, répétez ce bloc de code quatre fois. Ce bloc
repeat
met en forme une date, l'ajoute à la liste des options de date, puis incrémente le calendrier d'un jour.
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
- Renvoyez la
options
mise à jour à la fin de la méthode. Voici votre méthode complète :
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
val calendar = Calendar.getInstance()
// Create a list of dates starting with the current date and the following 3 dates
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
return options
}
- Dans la classe
OrderViewModel
, ajoutez une propriété de classe appeléedateOptions
qui est uneval
. Initialisez-la à l'aide de la méthodegetPickupOptions()
que vous venez de créer.
val dateOptions = getPickupOptions()
Modifier la mise en page pour afficher les options de retrait
Maintenant que les quatre dates de retrait sont disponibles dans le ViewModel, mettez à jour la mise en page fragment_pickup.xml
pour les afficher. Vous utiliserez également la liaison de données pour afficher l'état coché de chaque case d'option et pour mettre à jour la date dans le ViewModel lorsqu'une autre case d'option est sélectionnée. Cette implémentation est semblable à la liaison de données dans le fragment "flavor".
Dans fragment_pickup.xml
:
Case d'option option0
représentant dateOptions[0]
dans viewModel
(aujourd'hui)
Case d'option option1
représentant dateOptions[1]
dans viewModel
(demain)
Case d'option option2
représentant dateOptions[2]
dans viewModel
(le surlendemain)
Case d'option option3
représentant dateOptions[3]
en viewModel
(le lendemain du surlendemain)
- Dans
fragment_pickup.xml
, pour la case d'optionoption0
, utilisez la nouvelle variable de mise en pageviewModel
pour définir l'attributchecked
en fonction de la valeurdate
dans le ViewModel. Comparez la propriétéviewModel.date
à la première chaîne de la listedateOptions
, qui correspond à la date du jour. Utilisez la fonctionequals
pour comparer. L'expression de liaison finale se présente comme suit :
@{viewModel.date.equals(viewModel.dateOptions[0])}
- Pour la même case d'option, ajoutez un écouteur d'événements à l'aide d'une expression "listener binding" sur l'attribut
onClick
. Lorsque vous cliquez sur cette option, appelezsetDate()
sur leviewModel
, en transmettantdateOptions[0]
. - Pour la même case d'option, définissez la valeur de l'attribut
text
sur la première chaîne de la listedateOptions
.
<RadioButton
android:id="@+id/option0"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[0])}"
android:text="@{viewModel.dateOptions[0]}"
...
/>
- Répétez les étapes ci-dessus pour les autres cases d'option, et modifiez l'index de
dateOptions
en conséquence.
<RadioButton
android:id="@+id/option1"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[1])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[1])}"
android:text="@{viewModel.dateOptions[1]}"
... />
<RadioButton
android:id="@+id/option2"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[2])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[2])}"
android:text="@{viewModel.dateOptions[2]}"
... />
<RadioButton
android:id="@+id/option3"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[3])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[3])}"
android:text="@{viewModel.dateOptions[3]}"
... />
- Exécutez l'application. Les options de retrait disponibles correspondent alors aux prochains jours. Votre capture d'écran varie en fonction de la date du jour. Notez qu'aucune option n'est sélectionnée par défaut. Vous l'implémenterez à l'étape suivante.
- Dans la classe
OrderViewModel
, créez une fonction appeléeresetOrder()
pour réinitialiser les propriétésMutableLiveData
dans le ViewModel. Définissez la valeur de date actuelle de la listedateOptions
sur_date.
value.
.
fun resetOrder() {
_quantity.value = 0
_flavor.value = ""
_date.value = dateOptions[0]
_price.value = 0.0
}
- Ajoutez un bloc
init
à la classe et appelez la nouvelle méthoderesetOrder()
à partir de celle-ci.
init {
resetOrder()
}
- Supprimez les valeurs initiales de la déclaration des propriétés de la classe. Vous utilisez maintenant le bloc
init
pour initialiser les propriétés lorsqu'une instance deOrderViewModel
est créée.
private val _quantity = MutableLiveData<Int>()
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>()
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>()
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>()
val price: LiveData<Double> = _price
- Exécutez à nouveau votre application. Notez que la date du jour est sélectionnée par défaut.
Mettre à jour le fragment Résumé pour utiliser le ViewModel
Passons maintenant au dernier fragment. Le fragment "order_summary" est destiné à afficher un récapitulatif des détails de la commande. Dans cette tâche, vous allez exploiter toutes les informations de commande du ViewModel partagé et mettre à jour les détails des commandes à l'écran à l'aide de la liaison de données.
- Dans
fragment_summary.xml
, vérifiez que la variable de données du ViewModel,viewModel
, a été déclarée.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Dans
SummaryFragment
, dansonViewCreated()
, assurez-vous quebinding.viewModel
est initialisé. - Dans
fragment_summary.xml
, lisez le ViewModel pour mettre à jour l'écran avec le détail du récapitulatif de commande. Modifiez la quantité, le type de produit et la dateTextViews
en ajoutant les attributs textuels suivants. La quantité est du typeInt
. Vous devez donc la convertir en chaîne.
<TextView
android:id="@+id/quantity"
...
android:text="@{viewModel.quantity.toString()}"
... />
<TextView
android:id="@+id/flavor"
...
android:text="@{viewModel.flavor}"
... />
<TextView
android:id="@+id/date"
...
android:text="@{viewModel.date}"
... />
- Exécutez et testez l'application pour vérifier que les options de commande sélectionnées s'affichent dans le récapitulatif des commandes.
8. Calculer le prix à partir des détails de la commande
En regardant les dernières captures d'écran de cet atelier de programmation, vous remarquerez que le prix est affiché sur chaque fragment (à l'exception de StartFragment
) afin que l'utilisateur connaisse le prix lorsqu'il crée la commande.
Voici les règles de notre boutique de cupcakes pour calculer le prix.
- Chaque cupcake coûte 2 $.
- Le retrait le jour même ajoute 3 $ à la commande.
Par conséquent, pour une commande de six cupcakes, le prix est de six cupcakes x 2 $ chacun = 12 $. Si l'utilisateur souhaite retirer sa commande le jour même, le coût supplémentaire de 3 $ s'ajoute. Le montant total de la commande s'élève alors à 15 $.
Mettre à jour le prix dans le ViewModel
Pour que cette fonctionnalité soit disponible dans votre application, indiquez d'abord le prix par cupcake et ignorez le coût du retrait le jour même.
- Ouvrez
OrderViewModel.kt
et stockez le prix par cupcake dans une variable. Déclarez-la en tant que constante privée de premier niveau en haut du fichier, en dehors de la définition de classe (mais après les instructions d'importation). Utilisez le modificateurconst
, pour le transformer en modificateur en lecture seule, utilisezval
.
package ...
import ...
private const val PRICE_PER_CUPCAKE = 2.00
class OrderViewModel : ViewModel() {
...
Rappelez-vous que les valeurs constantes (identifiées par le mot clé const
en Kotlin) ne changent pas et que la valeur est connue au moment de la compilation. Pour en savoir plus sur les constantes, consultez la documentation.
- Maintenant que vous avez défini un prix par cupcake, créez une méthode d'assistance pour calculer le prix. Cette méthode peut être
private
, car elle n'est utilisée qu'au sein de cette classe. Vous allez modifier la logique de tarification pour inclure les frais de retrait le jour même.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
}
Cette ligne de code multiplie le prix par cupcake par le nombre de cupcakes commandés. Pour le code entre parenthèses, étant donné que la valeur de quantity.value
peut être nulle, utilisez un opérateur Elvis (?:
) . 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).
- Dans la même classe
OrderViewModel
, modifiez la variable de prix lorsque la quantité est définie. Appelez la nouvelle fonction dans la fonctionsetQuantity()
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
updatePrice()
}
Lier la propriété du prix à l'interface utilisateur
- Dans les mises en page pour
fragment_flavor.xml
,fragment_pickup.xml
etfragment_summary.xml
, assurez-vous que la variable de donnéesviewModel
de typecom.example.cupcake.model.OrderViewModel
est définie.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Dans la méthode
onViewCreated()
de chaque classe de fragment, veillez à lier l'instance d'objet du ViewModel dans le fragment à la variable de données du ViewModel dans la mise en page.
binding?.apply {
viewModel = sharedViewModel
...
}
- Dans chaque mise en page de fragment, définissez le prix à l'aide de la variable
viewModel
si celui-ci est affiché dans la mise en page. Commencez par modifier le fichierfragment_flavor.xml
. Pour l'affichage de textesubtotal
, définissez la valeur de l'attributandroid:text
sur"@{@string/subtotal_price(viewModel.price)}".
. Cette expression de liaison de données utilise la ressource de chaîne@string/subtotal_price
et transmet un paramètre (le prix depuis le ViewModel), de sorte que le résultat affiche Subtotal 12.0 (Sous-total 12,0), par exemple.
...
<TextView
android:id="@+id/subtotal"
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
Vous utilisez cette ressource de chaîne déjà déclarée dans le fichier strings.xml
:
<string name="subtotal_price">Subtotal %s</string>
- Exécutez l'application. Si vous sélectionnez One cupcake (Un cupcake) dans le fragment de départ, celui-ci affichera Subtotal 2.0 (Sous-total 2,0). Si vous sélectionnez Six cupcakes, le fragment affichera Subtotal 12.0 (Sous-total 12,0), etc. Vous définirez la devise appropriée pour le prix dans la suite de l'atelier. Ce comportement est donc normal pour l'instant.
- Effectuez maintenant une modification similaire pour les fragments "pickup" et "summary". Dans les mises en page
fragment_pickup.xml
etfragment_summary.xml
, modifiez les affichages de texte pour qu'ils utilisent également la propriétéviewModel
price
.
fragment_pickup.xml
...
<TextView
android:id="@+id/subtotal"
...
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
fragment_summary.xml
...
<TextView
android:id="@+id/total"
...
android:text="@{@string/total_price(viewModel.price)}"
... />
...
- Exécutez l'application. Assurez-vous que le prix affiché dans le récapitulatif de la commande est calculé correctement pour les quantités suivantes : 1, 6 et 12 cupcakes. Comme indiqué précédemment, il est normal que la mise en forme du prix ne soit pas correcte pour le moment (elle sera de 2,0 pour 2 $ ou de 12,0 pour 12 $).
Frais supplémentaires pour un retrait le jour même
Dans cette tâche, vous allez implémenter la deuxième règle selon laquelle le retrait le jour même ajoute 3 $ à la commande.
- Dans la classe
OrderViewModel
, définissez une nouvelle constante privée de premier niveau pour le coût du retrait le jour même.
private const val PRICE_FOR_SAME_DAY_PICKUP = 3.00
- Dans
updatePrice()
, vérifiez si l'utilisateur a sélectionné le retrait le jour même. Vérifiez que la date du ViewModel (_date.
value
) est identique au premier élément de la listedateOptions
, qui correspond toujours à la date du jour.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
if (dateOptions[0] == _date.value) {
}
}
- Pour faciliter ces calculs, introduisez une variable temporaire,
calculatedPrice
. Calculez le prix actualisé et réattribuez-lui la valeur_price.
value
.
private fun updatePrice() {
var calculatedPrice = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
// If the user selected the first option (today) for pickup, add the surcharge
if (dateOptions[0] == _date.value) {
calculatedPrice += PRICE_FOR_SAME_DAY_PICKUP
}
_price.value = calculatedPrice
}
- Appelez la méthode d'assistance
updatePrice()
depuis la méthodesetDate()
pour ajouter le coût du retrait le jour même.
fun setDate(pickupDate: String) {
_date.value = pickupDate
updatePrice()
}
- Exécutez votre application, puis parcourez-la. Vous remarquerez que la modification de la date de retrait n'entraîne pas la suppression du coût de retrait le jour même du prix total. En effet, le prix est modifié dans le ViewModel, mais il n'est pas notifié à la mise en page de la liaison.
Définir LifecycleOwner pour observer LiveData
LifecycleOwner
est une classe qui contient les informations sur l'état de cycle de vie d'un composant Android (comme une activité ou un fragment). Un observateur LiveData
n'observe les modifications apportées aux données de l'application que si le propriétaire du cycle de vie est actif (STARTED
ou RESUMED
).
Dans votre application, l'objet LiveData
ou les données observables correspondent à la propriété price
du ViewModel. Les propriétaires du cycle de vie sont les fragments "flavor", "pickup" et "summary". Les observateurs LiveData
sont les expressions de liaison des fichiers de mise en page qui comportent des données observables, comme le prix. Avec la liaison de données, lorsqu'une valeur observable change, les éléments d'interface utilisateur auxquels elle est associée sont mis à jour automatiquement.
Exemple d'expression de liaison : android:text="@{@string/subtotal_price(viewModel.price)}"
Pour que les éléments de l'interface utilisateur soient mis à jour automatiquement, vous devez associer binding.
lifecycleOwner
avec les propriétaires du cycle de vie dans l'application. Vous implémenterez ceci par la suite.
- Dans les classes
FlavorFragment
,PickupFragment
etSummaryFragment
, dans la méthodeonViewCreated()
, ajoutez le code suivant dans le blocbinding?.apply
. Le propriétaire du cycle de vie sera ainsi défini sur l'objet de liaison. En définissant le propriétaire du cycle de vie, l'application pourra observer des objetsLiveData
.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
...
}
- Exécutez à nouveau votre application. Sur l'écran de retrait, modifiez la date de retrait : vous remarquerez que le prix change automatiquement. Le coût de retrait de la commande s'affiche correctement sur l'écran récapitulatif.
- Notez que si vous sélectionnez la date de retrait en magasin le jour même, le prix de la commande augmente de 3 $. Le prix d'une date ultérieure doit toujours correspondre à la quantité de cupcakes x 2 $.
- Testez des quantités, des saveurs et des dates de retrait différentes. Le prix mis à jour à partir du ViewModel doit maintenant s'afficher pour chaque fragment. L'avantage, c'est que vous n'avez pas besoin d'écrire de code Kotlin supplémentaire pour que l'interface utilisateur se mette à jour à chaque changement.
Pour terminer l'implémentation de la fonctionnalité de prix, vous devez mettre en forme le prix dans la devise locale.
Modifier le format de prix avec la transformation LiveData
La ou les méthodes de transformation LiveData
permettent de manipuler des données sur la source LiveData
et de renvoyer un objet LiveData
obtenu. En d'autres termes, elle transforme la valeur de LiveData
en une autre valeur. Ces transformations ne sont pas calculées, sauf si un observateur observe l'objet LiveData
.
Transformations.map()
est une fonction de transformation. Cette méthode utilise la LiveData
source et une fonction en tant que paramètres. La fonction manipule la source LiveData
et renvoie une valeur mise à jour, qui est également observable.
Voici quelques exemples en temps réel pour lesquels vous pouvez utiliser une transformation LiveData :
- Mettre en forme et afficher les dates et les heures
- Trier une liste d'éléments
- Filtrer ou regrouper les éléments
- Calculer le résultat à partir d'une liste, comme la somme de plusieurs éléments, le nombre d'éléments, retourner au dernier élément, et ainsi de suite.
Dans cette tâche, vous allez utiliser la méthode Transformations.map()
pour mettre en forme le prix dans la devise locale. Vous allez transformer le prix d'origine qui s'affiche en valeur décimale (LiveData<Double>
) en une valeur de chaîne (LiveData<String>
).
- Dans la classe
OrderViewModel
, définissez le type de propriété de support surLiveData<String>
au lieu deLiveData<Double>.
. Le prix mis en forme sera une chaîne avec un symbole de devise comme "$". Vous corrigerez l'erreur d'initialisation à l'étape suivante.
private val _price = MutableLiveData<Double>()
val price: LiveData<String>
- Utilisez
Transformations.map()
pour initialiser la nouvelle variable, transmettre le_price
et une fonction lambda. Utilisez la méthodegetCurrencyInstance()
dans la classeNumberFormat
pour convertir le format du prix dans la devise locale. Le code de transformation se présente comme suit :
private val _price = MutableLiveData<Double>()
val price: LiveData<String> = Transformations.map(_price) {
NumberFormat.getCurrencyInstance().format(it)
}
Vous devez importer androidx.lifecycle.Transformations
et java.text.NumberFormat
.
- Exécutez l'application. La chaîne de prix mise en forme doit maintenant s'afficher pour le sous-total et le total. C'est beaucoup plus pratique pour l'utilisateur !
- Vérifiez que votre code fonctionne comme prévu. Exemples de scénarios : commandez un cupcake, puis six, puis 12. Assurez-vous que le prix se met à jour correctement sur chaque écran. La valeur doit indiquer Subtotal $2.00 (Sous-total 2 $) pour les fragments "flavor" et "pickup", et Total $2.00 (Total 2 $) pour le récapitulatif de la commande. Assurez-vous également que le récapitulatif de la commande contient les bonnes informations.
9. Configurer des écouteurs de clics à l'aide de "listener binding"
Dans cette tâche, vous allez utiliser l'expression "listener binding" pour lier les écouteurs de clics sur le bouton des classes de fragment à la mise en page.
- Dans le fichier de mise en page
fragment_start.xml
, ajoutez une variable de données appeléestartFragment
de typecom.example.cupcake.StartFragment
. Assurez-vous que le nom du package du fragment correspond au nom du package de votre application.
<layout ...>
<data>
<variable
name="startFragment"
type="com.example.cupcake.StartFragment" />
</data>
...
<ScrollView ...>
- Dans
StartFragment.kt
, dans la méthodeonViewCreated()
, liez la nouvelle variable de données à l'instance de fragment. Vous pouvez accéder à l'instance de fragment à l'intérieur du fragment à l'aide du mot cléthis
. Supprimez le blocbinding?.
apply
et le code qu'il contient. Une fois terminée, la méthode doit se présenter comme suit :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.startFragment = this
}
- Dans
fragment_start.xml
, ajoutez des écouteurs d'événements à l'aide d'une expression "listener binding" à l'attribut 'onClick
pour les boutons, appelezorderCupcake()
surstartFragment
en indiquant le nombre de cupcakes.
<Button
android:id="@+id/order_one_cupcake"
android:onClick="@{() -> startFragment.orderCupcake(1)}"
... />
<Button
android:id="@+id/order_six_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(6)}"
... />
<Button
android:id="@+id/order_twelve_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(12)}"
... />
- Exécutez l'application. Notez que les gestionnaires de clics sur les boutons du startFragment fonctionnent comme prévu.
- De même, ajoutez la variable de données ci-dessus dans d'autres mises en page pour lier l'instance de fragment
fragment_flavor.xml
,fragment_pickup.xml
etfragment_summary.xml
.
Dans fragment_flavor.xml
<layout ...>
<data>
<variable
... />
<variable
name="flavorFragment"
type="com.example.cupcake.FlavorFragment" />
</data>
<ScrollView ...>
Dans fragment_pickup.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="pickupFragment"
type="com.example.cupcake.PickupFragment" />
</data>
<ScrollView ...>
Dans fragment_summary.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="summaryFragment"
type="com.example.cupcake.SummaryFragment" />
</data>
<ScrollView ...>
- Dans les autres classes de fragments, supprimez le code qui définit manuellement l'écouteur de clics sur les boutons dans les méthodes
onViewCreated()
. - Dans les méthodes
onViewCreated()
, la variable de données de fragment est liée à l'instance de fragment. Vous allez utiliser le mot cléthis
différemment, car dans le blocbinding?.apply
, le mot cléthis
fait référence à l'instance de liaison, et non à l'instance de fragment. Utilisez@
et spécifiez le nom de la classe du fragment de manière explicite, par exemplethis@FlavorFragment
. Une fois terminées, les méthodesonViewCreated()
devraient se présenter comme suit :
La méthode onViewCreated()
dans la classe FlavorFragment
devrait se présenter comme suit :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
flavorFragment = this@FlavorFragment
}
}
La méthode onViewCreated()
dans la classe PickupFragment
devrait se présenter comme suit :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
pickupFragment = this@PickupFragment
}
}
La méthode onViewCreated()
obtenue dans la méthode de classe SummaryFragment
devrait se présenter comme suit :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
summaryFragment = this@SummaryFragment
}
}
- De même, dans les autres fichiers de mise en page, ajoutez des expressions de "listener binding" à l'attribut
onClick
pour les boutons.
Dans fragment_flavor.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> flavorFragment.goToNextScreen()}"
... />
Dans fragment_pickup.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> pickupFragment.goToNextScreen()}"
... />
Dans fragment_summary.xml
:
<Button
android:id="@+id/send_button"
android:onClick="@{() -> summaryFragment.sendOrder()}"
...>
- Exécutez l'application pour vérifier que les boutons fonctionnent toujours comme prévu. Le comportement ne devrait pas changer, mais vous avez maintenant configuré des écouteurs de clics à l'aide d'expressions "listener binding".
Bravo ! Vous avez terminé cet atelier de programmation et l'application Cupcake est prête à l'emploi. Toutefois, l'application n'est pas encore terminée. Dans l'atelier de programmation suivant, vous ajouterez un bouton Annuler et modifierez la pile "Retour". Vous découvrirez également ce qu'est une pile "Retour" et d'autres sujets inédits. À bientôt !
10. Code de solution
Le code de solution de cet atelier de programmation figure dans le projet ci-dessous. Utilisez la branche viewmodel pour extraire ou télécharger le code.
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.
11. Résumé
- Le
ViewModel
fait partie des composants d'architecture Android, et les données de l'application enregistrées dans leViewModel
sont conservées lors des modifications de configuration. Pour ajouter unViewModel
à votre application, vous devez créer une classe basée sur la classeViewModel
. - Le
ViewModel
partagé permet d'enregistrer les données de l'application à partir de plusieurs fragments dans un seulViewModel
. Plusieurs fragments de l'application accéderont auViewModel
partagé en fonction du champ d'application de leur activité. LifecycleOwner
est une classe qui contient les informations sur l'état de cycle de vie d'un composant Android (comme une activité ou un fragment).- Un observateur
LiveData
n'observe les modifications apportées aux données de l'application que si le propriétaire du cycle de vie est actif (STARTED
ouRESUMED
). - Les expressions "listener binding" sont des expressions lambda qui s'exécutent lorsqu'un événement se produit, comme lors d'un événement
onClick
. Elles sont semblables aux références de méthodes telles quetextview.setOnClickListener(clickListener)
, mais elles vous permettent d'exécuter des expressions de liaison de données arbitraires. - La ou les méthodes de transformation
LiveData
permettent de manipuler des données sur la sourceLiveData
et de renvoyer un objetLiveData
obtenu. - Les frameworks Android fournissent une classe appelée
SimpleDateFormat
, qui permet de mettre en forme et d'analyser les dates en tenant compte des paramètres régionaux. Elle permet de mettre en forme (date → texte) et d'analyser les dates (texte → date).