Principes de base d'Android en Kotlin 05.3 : Liaison de données avec ViewModel et LiveData

Cet atelier de programmation fait partie du cours Principes de base d'Android en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.

Introduction

Dans les précédents ateliers de programmation de cette leçon, vous avez amélioré le code de l'application GuessTheWord. L'application utilise désormais des objets ViewModel, ce qui permet aux données de l'application de survivre aux modifications de la configuration de l'appareil, telles que les rotations d'écran et les modifications de la disponibilité du clavier. Vous avez également ajouté LiveData observable, de sorte que les vues sont automatiquement averties lorsque les données observées changent.

Dans cet atelier de programmation, vous allez continuer à travailler avec l'application GuessTheWord. Vous allez lier des vues aux classes ViewModel de l'application afin que les vues de votre mise en page communiquent directement avec les objets ViewModel. (Jusqu'à présent, les vues de votre application communiquaient indirectement avec ViewModel, par le biais des fragments de l'application.) Une fois que vous avez intégré la liaison de données aux objets ViewModel, vous n'avez plus besoin de gestionnaires de clics dans les fragments de l'application. Vous pouvez donc les supprimer.

Vous modifiez également l'application GuessTheWord pour qu'elle utilise LiveData comme source de liaison de données afin d'informer l'UI des modifications apportées aux données, sans utiliser les méthodes d'observation LiveData.

Ce que vous devez déjà savoir

  • Vous savez comment créer des applications Android de base en langage Kotlin.
  • Vous comprenez le fonctionnement des cycles de vie des activités et des fragments.
  • Comment utiliser les objets ViewModel dans votre application.
  • Comment stocker des données à l'aide de LiveData dans un ViewModel.
  • Comment ajouter des méthodes d'observation pour voir les modifications apportées aux données LiveData.

Points abordés

  • Utiliser les éléments de la bibliothèque Data Binding.
  • Comment intégrer ViewModel avec la liaison de données.
  • Comment intégrer LiveData avec la liaison de données.
  • Utiliser les liaisons d'écouteur pour remplacer les écouteurs de clics dans un fragment.
  • Comment ajouter la mise en forme de chaînes aux expressions de liaison de données.

Objectifs de l'atelier

  • Les vues des mises en page GuessTheWord communiquent indirectement avec les objets ViewModel, en utilisant des contrôleurs d'UI (fragments) pour transmettre les informations. Dans cet atelier de programmation, vous allez lier les vues de l'application aux objets ViewModel afin que les vues communiquent directement avec les objets ViewModel.
  • Vous modifiez l'application pour qu'elle utilise LiveData comme source de liaison de données. Après ce changement, les objets LiveData informent l'UI des modifications apportées aux données, et les méthodes d'observation LiveData ne sont plus nécessaires.

Dans les ateliers de programmation de la leçon 5, vous développez l'application GuessTheWord, en commençant par le code de démarrage. GuessTheWord est un jeu de charades à deux joueurs, dans lequel les joueurs collaborent pour obtenir le meilleur score possible.

Le premier joueur regarde les mots dans l'application et mime chacun d'eux à tour de rôle, en veillant à ne pas les montrer au deuxième joueur. Le deuxième joueur essaie de deviner le mot.

Pour jouer, le premier joueur ouvre l'application sur l'appareil et voit un mot, par exemple "guitare", comme indiqué sur la capture d'écran ci-dessous.

Le premier joueur mime le mot, en veillant à ne pas le prononcer.

  • Lorsque le deuxième joueur devine le mot, le premier joueur appuie sur le bouton OK, ce qui incrémente le compteur et affiche le mot suivant.
  • Si le deuxième joueur ne parvient pas à deviner le mot, le premier joueur appuie sur le bouton Passer, ce qui diminue le nombre de mots d'un et passe au mot suivant.
  • Pour mettre fin à la partie, appuyez sur le bouton End Game (Mettre fin à la partie). (Cette fonctionnalité n'est pas incluse dans le code de démarrage du premier atelier de programmation de la série.)

Dans cet atelier de programmation, vous allez améliorer l'application GuessTheWord en intégrant la liaison de données avec LiveData dans les objets ViewModel. Cela automatise la communication entre les vues de la mise en page et les objets ViewModel, et vous permet de simplifier votre code en utilisant LiveData.

Écran titre

Écran de jeu

Écran de score

Dans cette tâche, vous allez localiser et exécuter le code de démarrage de cet atelier de programmation. Vous pouvez utiliser l'application GuessTheWord que vous avez créée dans l'atelier de programmation précédent comme code de démarrage, ou télécharger une application de démarrage.

  1. (Facultatif) Si vous n'utilisez pas votre code de l'atelier de programmation précédent, téléchargez le code de démarrage pour cet atelier de programmation. Décompressez le code, puis ouvrez le projet dans Android Studio.
  2. Exécutez l'application et jouez.
  3. Notez que le bouton OK affiche le mot suivant et augmente le score de un, tandis que le bouton Ignorer affiche le mot suivant et diminue le score de un. Le bouton End Game (Terminer la partie) met fin à la partie.
  4. Parcourez tous les mots et remarquez que l'application accède automatiquement à l'écran du score.

Dans un précédent atelier de programmation, vous avez utilisé la liaison de données comme moyen sûr d'accéder aux vues dans l'application GuessTheWord. Mais la véritable puissance de la liaison de données réside dans ce que son nom suggère : lier les données directement aux objets de vue de votre application.

Architecture actuelle de l'application

Dans votre application, les vues sont définies dans la mise en page XML, et les données de ces vues sont conservées dans des objets ViewModel. Entre chaque vue et son ViewModel correspondant se trouve un contrôleur d'UI, qui sert de relais entre eux.

Exemple :

  • Le bouton OK est défini comme une vue Button dans le fichier de mise en page game_fragment.xml.
  • Lorsque l'utilisateur appuie sur le bouton Got It (OK), un écouteur de clics dans le fragment GameFragment appelle l'écouteur de clics correspondant dans GameViewModel.
  • Le score est mis à jour dans GameViewModel.

Les vues Button et GameViewModel ne communiquent pas directement. Elles ont besoin du listener de clic qui se trouve dans GameFragment.

ViewModel transmis à la liaison de données

Il serait plus simple que les vues de la mise en page communiquent directement avec les données des objets ViewModel, sans s'appuyer sur des contrôleurs d'UI comme intermédiaires.

Les objets ViewModel contiennent toutes les données d'UI de l'application GuessTheWord. En transmettant des objets ViewModel dans la liaison de données, vous pouvez automatiser une partie de la communication entre les vues et les objets ViewModel.

Dans cette tâche, vous allez associer les classes GameViewModel et ScoreViewModel à leurs mises en page XML correspondantes. Vous avez également configuré des liaisons d'écouteur pour gérer les événements de clic.

Étape 1 : Ajoutez la liaison de données pour GameViewModel

Dans cette étape, vous associez GameViewModel au fichier de mise en page correspondant, game_fragment.xml.

  1. Dans le fichier game_fragment.xml, ajoutez une variable de liaison de données de type GameViewModel. Si vous rencontrez des erreurs dans Android Studio, nettoyez et recompilez le projet.
<layout ...>

   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  
   <androidx.constraintlayout...
  1. Dans le fichier GameFragment, transmettez GameViewModel à la liaison de données.

    Pour ce faire, attribuez viewModel à la variable binding.gameViewModel que vous avez déclarée à l'étape précédente. Placez ce code dans onCreateView(), après l'initialisation de viewModel. Si vous rencontrez des erreurs dans Android Studio, nettoyez et recompilez le projet.
// Set the viewmodel for databinding - this allows the bound layout access 
// to all the data in the ViewModel
binding.gameViewModel = viewModel

Étape 2 : Utilisez des expressions "listener binding" pour la gestion des événements

Les expressions "listener binding" sont des expressions de liaison qui s'exécutent lorsque des événements tels que onClick(), onZoomIn() ou onZoomOut() sont déclenchés. Les expressions "listener binding" sont écrites sous la forme d'expressions lambda.

La liaison de données crée un écouteur et le définit sur la vue. Lorsque l'événement écouté se produit, l'écouteur évalue l'expression lambda. Les expressions "listener binding" fonctionnent avec le plug-in Android Gradle version 2.0 ou ultérieure. Pour en savoir plus, consultez Mises en page et expressions de liaison.

Dans cette étape, vous allez remplacer les écouteurs de clics dans GameFragment par des liaisons d'écouteur dans le fichier game_fragment.xml.

  1. Dans game_fragment.xml, ajoutez l'attribut onClick à skip_button. Définissez une expression de liaison et appelez la méthode onSkip() dans GameViewModel. Cette expression de liaison est appelée listener binding.
<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />
  1. De même, liez l'événement de clic de correct_button à la méthode onCorrect() dans GameViewModel.
<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />
  1. Liez l'événement de clic de end_game_button à la méthode onGameFinish() dans GameViewModel.
<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />
  1. Dans GameFragment, supprimez les instructions qui définissent les écouteurs de clics, ainsi que les fonctions que les écouteurs de clics appellent. Vous n'en avez plus besoin.

Code à supprimer :

binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }

/** Methods for buttons presses **/
private fun onSkip() {
   viewModel.onSkip()
}
private fun onCorrect() {
   viewModel.onCorrect()
}
private fun onEndGame() {
   gameFinished()
}

Étape 3 : Ajoutez la liaison de données pour ScoreViewModel

Dans cette étape, vous associez ScoreViewModel au fichier de mise en page correspondant, score_fragment.xml.

  1. Dans le fichier score_fragment.xml, ajoutez une variable de liaison de type ScoreViewModel. Cette étape est semblable à celle que vous avez suivie pour GameViewModel ci-dessus.
<layout ...>
   <data>
       <variable
           name="scoreViewModel"
           type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout
  1. Dans score_fragment.xml, ajoutez l'attribut onClick à play_again_button. Définissez une liaison d'écouteur et appelez la méthode onPlayAgain() dans ScoreViewModel.
<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />
  1. Dans ScoreFragment, à l'intérieur de onCreateView(), initialisez viewModel. Initialisez ensuite la variable de liaison binding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel
  1. Dans ScoreFragment, supprimez le code qui définit l'écouteur de clics pour playAgainButton. Si Android Studio affiche une erreur, nettoyez et recompilez le projet.

Code à supprimer :

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Exécutez votre application. Elle devrait fonctionner comme auparavant, mais les vues de bouton communiquent désormais directement avec les objets ViewModel. Les vues ne communiquent plus via les gestionnaires de clics sur les boutons dans ScoreFragment.

Résoudre les problèmes liés aux messages d'erreur de liaison de données

Lorsqu'une application utilise la liaison de données, le processus de compilation génère des classes intermédiaires qui sont utilisées pour la liaison de données. Une application peut comporter des erreurs qu'Android Studio ne détecte que lorsque vous essayez de la compiler. Vous ne voyez donc pas d'avertissements ni de code rouge lorsque vous écrivez le code. Toutefois, au moment de la compilation, vous obtenez des erreurs cryptiques provenant des classes intermédiaires générées.

Si vous recevez un message d'erreur obscur :

  1. Examinez attentivement le message dans le volet Compilation d'Android Studio. Si vous voyez un emplacement qui se termine par databinding, cela signifie qu'il y a une erreur de liaison de données.
  2. Dans le fichier XML de mise en page, recherchez les erreurs dans les attributs onClick qui utilisent la liaison de données. Recherchez la fonction appelée par l'expression lambda et assurez-vous qu'elle existe.
  3. Dans la section <data> du fichier XML, vérifiez l'orthographe de la variable de liaison de données.

Par exemple, notez la faute d'orthographe dans le nom de la fonction onCorrect() dans la valeur d'attribut suivante :

android:onClick="@{() -> gameViewModel.onCorrectx()}"

Notez également la faute d'orthographe dans gameViewModel dans la section <data> du fichier XML :

<data>
   <variable
       name="gameViewModelx"
       type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>

Android Studio ne détecte pas ces erreurs tant que vous n'avez pas compilé l'application. Le compilateur affiche alors un message d'erreur semblable à celui-ci :

error: cannot find symbol
import com.example.android.guesstheword.databinding.GameFragmentBindingImpl"

symbol:   class GameFragmentBindingImpl
location: package com.example.android.guesstheword.databinding

La liaison de données fonctionne bien avec LiveData utilisé avec les objets ViewModel. Maintenant que vous avez ajouté la liaison de données aux objets ViewModel, vous êtes prêt à intégrer LiveData.

Dans cette tâche, vous allez modifier l'application GuessTheWord pour qu'elle utilise LiveData comme source de liaison de données afin d'informer l'UI des modifications apportées aux données, sans utiliser les méthodes d'observation LiveData.

Étape 1 : Ajoutez LiveData au mot dans le fichier game_fragment.xml

Au cours de cette étape, vous allez lier l'affichage du mot actuel directement à l'objet LiveData dans ViewModel.

  1. Dans game_fragment.xml, ajoutez l'attribut android:text à l'affichage de texte word_text.

Définissez-le sur l'objet LiveData, word à partir de GameViewModel, à l'aide de la variable de liaison gameViewModel.

<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{gameViewModel.word}"
   ... />

Notez que vous n'avez pas besoin d'utiliser word.value. Vous pouvez utiliser l'objet LiveData réel à la place. L'objet LiveData affiche la valeur actuelle de word. Si la valeur de word est nulle, l'objet LiveData affiche une chaîne vide.

  1. Dans GameFragment, dans onCreateView(), après avoir initialisé gameViewModel, définissez l'activité actuelle comme propriétaire du cycle de vie de la variable binding. Cela définit le champ d'application de l'objet LiveData ci-dessus, ce qui permet à l'objet de mettre à jour automatiquement les vues dans la mise en page, game_fragment.xml.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. Dans GameFragment, supprimez l'observateur pour LiveData word.

Code à supprimer :

/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})
  1. Exécutez votre application et jouez. Le mot actuel est désormais mis à jour sans méthode d'observateur dans le contrôleur d'UI.

Étape 2 : Ajoutez score LiveData au fichier score_fragment.xml

Au cours de cette étape, vous allez lier le LiveData score à l'affichage du score dans le fragment de score.

  1. Dans score_fragment.xml, ajoutez l'attribut android:text à l'affichage de texte du score. Attribuez scoreViewModel.score à l'attribut text. Étant donné que score est un entier, convertissez-le en chaîne à l'aide de String.valueOf().
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{String.valueOf(scoreViewModel.score)}"
   ... />
  1. Dans ScoreFragment, après avoir initialisé scoreViewModel, définissez l'activité actuelle comme propriétaire du cycle de vie de la variable binding.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. Dans ScoreFragment, supprimez l'observateur pour l'objet score.

Code à supprimer :

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Exécutez votre application et jouez. Notez que le score dans le fragment de score s'affiche correctement, sans observateur dans le fragment de score.

Étape 3 : Ajouter la mise en forme de chaîne avec la liaison de données

Dans la mise en page, vous pouvez ajouter la mise en forme de chaîne avec la liaison de données. Dans cette tâche, vous allez mettre en forme le mot actuel pour l'entourer de guillemets. Vous devez également mettre en forme la chaîne de score en y ajoutant le préfixe Current Score, comme illustré dans l'image suivante.

  1. Dans string.xml, ajoutez les chaînes suivantes, que vous utiliserez pour mettre en forme les affichages de texte word et score. %s et %d sont les espaces réservés pour le mot et le score actuels.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
  1. Dans game_fragment.xml, mettez à jour l'attribut text de l'affichage de texte word_text pour utiliser la ressource de chaîne quote_format. Transmettez gameViewModel.word. Cela transmet le mot actuel en tant qu'argument à la chaîne de mise en forme.
<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{@string/quote_format(gameViewModel.word)}"
   ... />
  1. Mettez en forme la vue de texte score de la même manière que word_text. Dans game_fragment.xml, ajoutez l'attribut text à l'affichage de texte score_text. Utilisez la ressource de chaîne score_format, qui accepte un argument numérique représenté par l'espace réservé %d. Transmettez l'objet LiveData, score, en tant qu'argument à cette chaîne de mise en forme.
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{@string/score_format(gameViewModel.score)}"
   ... />
  1. Dans la classe GameFragment, à l'intérieur de la méthode onCreateView(), supprimez le code d'observateur score.

Code à supprimer :

viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Nettoyez, recompilez et exécutez votre application, puis jouez. Notez que le mot actuel et le score sont mis en forme sur l'écran du jeu.

Félicitations ! Vous avez intégré LiveData et ViewModel à la liaison de données dans votre application. Cela permet aux vues de votre mise en page de communiquer directement avec ViewModel, sans utiliser de gestionnaires de clics dans le fragment. Vous avez également utilisé des objets LiveData comme source de liaison de données pour informer automatiquement l'UI des modifications apportées aux données, sans les méthodes d'observateur LiveData.

Projet Android Studio : GuessTheWord

  • La bibliothèque Data Binding fonctionne parfaitement avec les composants d'architecture Android tels que ViewModel et LiveData.
  • Les mises en page de votre application peuvent être liées aux données des composants d'architecture, qui vous aident déjà à gérer le cycle de vie du contrôleur d'UI et à vous informer des modifications apportées aux données.

Liaison de données ViewModel

  • Vous pouvez associer un ViewModel à une mise en page à l'aide de la liaison de données.
  • Les objets ViewModel contiennent les données d'UI. En transmettant des objets ViewModel dans la liaison de données, vous pouvez automatiser une partie de la communication entre les vues et les objets ViewModel.

Pour associer un ViewModel à une mise en page :

  • Dans le fichier de mise en page, ajoutez une variable de liaison de données de type ViewModel.
   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  • Dans le fichier GameFragment, transmettez GameViewModel à la liaison de données.
binding.gameViewModel = viewModel

Expressions "listener binding"

  • Les liaisons d'écouteur sont des expressions de liaison dans la mise en page qui s'exécutent lorsque des événements de clic tels que onClick() sont déclenchés.
  • Les expressions "listener binding" sont écrites sous la forme d'expressions lambda.
  • À l'aide des liaisons d'écouteur, vous remplacez les écouteurs de clics dans les contrôleurs d'UI par des liaisons d'écouteur dans le fichier de mise en page.
  • La liaison de données crée un écouteur et le définit sur la vue.
 android:onClick="@{() -> gameViewModel.onSkip()}"

Ajouter LiveData à la liaison de données

  • Les objets LiveData peuvent être utilisés comme source de liaison de données pour informer automatiquement l'UI des modifications apportées aux données.
  • Vous pouvez lier la vue directement à l'objet LiveData dans ViewModel. Lorsque le LiveData du ViewModel change, les vues de la mise en page peuvent être automatiquement mises à jour, sans les méthodes d'observation des contrôleurs d'UI.
android:text="@{gameViewModel.word}"
  • Pour que la liaison de données LiveData fonctionne, définissez l'activité actuelle (le contrôleur d'UI) comme propriétaire du cycle de vie de la variable binding dans le contrôleur d'UI.
binding.lifecycleOwner = this

Mise en forme de chaînes avec la liaison de données

  • Grâce à la liaison de données, vous pouvez mettre en forme une ressource de chaîne avec des espaces réservés tels que %s pour les chaînes et %d pour les nombres entiers.
  • Pour mettre à jour l'attribut text de la vue, transmettez l'objet LiveData en tant qu'argument à la chaîne de mise en forme.
 android:text="@{@string/quote_format(gameViewModel.word)}"

Cours Udacity :

Documentation pour les développeurs Android :

Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :

  • Attribuer des devoirs si nécessaire
  • Indiquer aux élèves comment rendre leurs devoirs
  • Noter les devoirs

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.

Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.

Répondre aux questions suivantes

Question 1

Parmi les affirmations suivantes concernant les expressions "listener binding", laquelle est fausse ?

  • Les expressions "listener binding" sont des expressions de liaison qui s'exécutent lorsqu'un événement se produit.
  • Les expressions "listener binding" fonctionnent avec toutes les versions du plug-in Android Gradle.
  • Les expressions "listener binding" sont écrites sous la forme d'expressions lambda.
  • Les expressions "listener binding" sont semblables aux références de méthodes, mais elles vous permettent d'exécuter des expressions de liaison de données arbitraires.

Question 2

Supposons que votre application comprenne cette ressource de chaîne :
<string name="generic_name">Hello %s</string>

Parmi les syntaxes suivantes, laquelle permet de formater la chaîne à l'aide de l'expression de liaison de données ?

  • android:text= "@{@string/generic_name(user.name)}"
  • android:text= "@{string/generic_name(user.name)}"
  • android:text= "@{@generic_name(user.name)}"
  • android:text= "@{@string/generic_name,user.name}"

Question 3

À quel moment une expression de type "listener-binding" est-elle évaluée et exécutée ?

  • Lorsque les données stockées dans LiveData sont modifiées
  • Lorsqu'une activité est recréée suite à une modification de configuration
  • Lorsqu'un événement tel que onClick() se produit
  • Lorsque l'activité passe en arrière-plan

Passez à la leçon suivante : 5.4 : Transformations de LiveData

Pour obtenir des liens vers d'autres ateliers de programmation de ce cours, consultez la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.