Utiliser LiveData avec ViewModel

1. Avant de commencer

Dans les précédents ateliers de programmation, vous avez appris à utiliser un élément ViewModel pour stocker les données de l'application. ViewModel permet aux données de l'application de "survivre" aux changements de configuration. Dans cet atelier de programmation, vous allez apprendre à intégrer LiveData aux données de ViewModel.

LiveData est une classe de conteneur de données qui fait également partie des composants d'architecture Android et qui peut être observée.

Conditions préalables

  • Vous savez comment télécharger le code source depuis GitHub et l'ouvrir dans Android Studio.
  • Vous savez comment créer et exécuter une application Android de base en langage Kotlin, à l'aide d'activités et de fragments.
  • Vous comprenez le fonctionnement des cycles de vie des activités et des fragments.
  • Vous savez comment conserver les données de l'UI en modifiant la configuration de l'appareil à l'aide de ViewModel.
  • Vous savez comment écrire des expressions lambda.

Points abordés

  • Comment utiliser LiveData et MutableLiveData dans votre application.
  • Comment encapsuler les données stockées dans un ViewModel avec LiveData.
  • Comment ajouter des méthodes d'observation pour voir les modifications effectuées dans LiveData..
  • Comment écrire des expressions de liaison dans un fichier de mise en page.

Objectifs de l'atelier

  • Utiliser LiveData pour les données de l'application (mot, nombre de mots et score) dans l'application Unscramble.
  • Ajouter des méthodes d'observation qui reçoivent une notification lorsque les données changent, et mettre à jour automatiquement l'affichage de texte du mot brouillé.
  • Écrire, dans le fichier de mise en page, des expressions de liaison qui sont déclenchées lorsque la classe LiveData sous-jacente est modifiée. Le score, le nombre de mots et les affichages de texte des mots brouillés sont mis à jour automatiquement.

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio.
  • Code de solution de l'atelier de programmation précédent (application Unscramble avec ViewModel).

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

Cet atelier de programmation utilise l'application Unscramble que vous avez créée dans l'atelier précédent (Stocker des données dans ViewModel) comme code de démarrage.

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

Cet atelier de programmation utilise le code de solution Unscramble que vous avez découvert lors de l'atelier précédent. L'application affiche un mot brouillé que le joueur doit déchiffrer. Le nombre de tentatives est illimité. Les données de l'application, telles que le mot actuel, le score du joueur et le nombre de mots, sont enregistrées dans ViewModel. Toutefois, l'UI de l'application n'affiche pas les nouvelles valeurs de score et de nombre de mots. Dans cet atelier de programmation, vous allez implémenter les fonctionnalités manquantes à l'aide de LiveData.

a20e6e45e0d5dc6f.png

3. Qu'est-ce que LiveData ?

LiveData est une classe de conteneur de données observable, sensible au cycle de vie.

Voici quelques caractéristiques de LiveData :

  • LiveData contient des données ; LiveData est un wrapper qui peut être utilisé avec n'importe quel type de données.
  • LiveData est une classe observable, ce qui signifie qu'un observateur est averti en cas de modification des données détenues par l'objet LiveData.
  • LiveData est sensible au cycle de vie. Lorsque vous associez un observateur à LiveData, il est associé à un LifecycleOwner (il s'agit généralement d'une activité ou d'un fragment). LiveData ne met à jour que les observateurs dont l'état de cycle de vie est actif, tels que STARTED ou RESUMED. Pour en savoir plus sur LiveData et sur l'observation, cliquez ici.

Mise à jour de l'UI dans le code de démarrage

Dans le code de démarrage, la méthode updateNextWordOnScreen() est appelée explicitement chaque fois que vous souhaitez afficher un nouveau mot brouillé dans l'UI. Cette méthode est appelée pendant l'initialisation du jeu et lorsque le joueur appuie sur le bouton Submit (Envoyer) ou Skip (Ignorer). L'appel est effectué à partir des méthodes onViewCreated(), restartGame(), onSkipWord() et onSubmitWord(). Avec Livedata, il n'est pas nécessaire d'appeler cette méthode depuis plusieurs emplacements pour mettre à jour l'UI. Vous n'effectuez cette opération qu'une seule fois dans l'observateur.

4. Ajouter LiveData au mot brouillé actuel

Dans cette tâche, vous allez apprendre à encapsuler des données avec LiveData, en convertissant le mot actuel dans GameViewModel en LiveData. Dans une tâche ultérieure, vous ajouterez un observateur à ces objets LiveData et vous apprendrez à observer LiveData.

MutableLiveData

MutableLiveData est la version modifiable de la classe LiveData. En d'autres termes, la valeur des données qu'elle contient peut être modifiée.

  1. Dans GameViewModel, remplacez le type de la variable _currentScrambledWord par MutableLiveData<String>. LiveData et MutableLiveData sont des classes génériques. Vous devez donc spécifier le type de données qu'elles contiennent.
  2. Définissez le type de variable _currentScrambledWord sur val, car la valeur de l'objet LiveData/MutableLiveData restera la même, et seules les données stockées dans l'objet seront modifiées.
private val _currentScrambledWord = MutableLiveData<String>()
  1. Remplacez le type currentScrambledWord du champ de stockage par LiveData<String>, car il est immuable. Android Studio affiche des erreurs que vous corrigerez lors des prochaines étapes.
val currentScrambledWord: LiveData<String>
   get() = _currentScrambledWord
  1. Pour accéder aux données d'un objet LiveData, utilisez la propriété value. Dans GameViewModel, dans le bloc else de la méthode getNextWord(), remplacez la référence de _currentScrambledWord par _currentScrambledWord.value.
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

5. Associer un observateur à l'objet LiveData

Dans cette tâche, vous allez configurer un observateur dans le composant d'application GameFragment. L'observateur que vous allez ajouter observe les modifications apportées aux données de l'application currentScrambledWord. LiveData tient compte du cycle de vie, ce qui signifie qu'il ne met à jour que les observateurs dont l'état du cycle de vie est actif. Ainsi, l'observateur situé dans GameFragment n'est averti que lorsque l'état de GameFragment est STARTED ou RESUMED.

  1. Dans GameFragment, supprimez la méthode updateNextWordOnScreen() et tous les appels associés. Vous n'avez pas besoin de cette méthode, car vous allez associer un observateur à LiveData.
  2. Dans onSubmitWord(), modifiez le bloc if-else vide comme suit. La méthode complète doit ressembler à ceci :
private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}
  1. Associez un observateur pour currentScrambledWord LiveData. Dans GameFragment, à la fin du rappel onViewCreated(), appelez la méthode observe() sur currentScrambledWord.
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

Android Studio affiche une erreur pour indiquer qu'il manque des paramètres. Vous corrigerez cette erreur à l'étape suivante.

  1. Transmettez viewLifecycleOwner en tant que premier paramètre à la méthode observe(). viewLifecycleOwner représente le cycle de vie de la vue de Fragment. Ce paramètre aide LiveData à prendre en compte le cycle de vie GameFragment et à n'avertir l'observateur que lorsque GameFragment est dans un état actif (STARTED ou RESUMED).
  2. Ajoutez un lambda en tant que deuxième paramètre avec newWord comme paramètre de fonction. newWord contiendra la nouvelle valeur du mot brouillé.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
   })

Une expression lambda est une fonction anonyme non déclarée, mais transmise immédiatement en tant qu'expression. Une expression lambda est toujours placée entre accolades { }.

  1. Dans le corps de fonction de l'expression lambda, affectez newWord à l'affichage de texte du mot brouillé.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Compilez et exécutez l'application. Votre jeu devrait fonctionner exactement comme auparavant, si ce n'est que l'affichage du mot brouillé est maintenant mis à jour automatiquement dans l'observateur LiveData, et non dans la méthode updateNextWordOnScreen().

6. Associer l'observateur au score et au nombre de mots

Comme dans la tâche précédente, vous allez ajouter LiveData aux autres données de l'application (le score et le nombre de mots) afin que l'UI soit mise à jour avec les valeurs correctes du score et du nombre de mots en cours de partie.

Étape 1 : Encapsulez le score et le nombre de mots avec LiveData

  1. Dans GameViewModel, remplacez le type des variables de classe _score et _currentWordCount par val.
  2. Remplacez le type de données des variables _score et _currentWordCount par MutableLiveData, puis initialisez-les sur 0.
  3. Définissez le type des champs de stockage sur LiveData<Int>.
private val _score = MutableLiveData(0)
val score: LiveData<Int>
   get() = _score

private val _currentWordCount = MutableLiveData(0)
val currentWordCount: LiveData<Int>
   get() = _currentWordCount
  1. Dans GameViewModel, au début de la méthode reinitializeData(), remplacez la référence de _score et _currentWordCount par _score.value et _currentWordCount.value, respectivement.
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. Dans GameViewModel, à l'intérieur de la méthode nextWord(), remplacez la référence de _currentWordCount par _currentWordCount.value!!.
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. Dans GameViewModel, à l'intérieur des méthodes increaseScore() et getNextWord(), remplacez la référence de _score et _currentWordCount par _score.value et _currentWordCount.value, respectivement. Android Studio affiche une erreur, car _score n'est plus un entier, mais LiveData. Vous allez résoudre ce problème lors des prochaines étapes.
  2. Utilisez la fonction Kotlin plus() pour augmenter la valeur _score. Cette fonction effectue l'ajout avec une sécurité nulle (null-safety).
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. De même, utilisez la fonction Kotlin inc() pour augmenter la valeur d'une unité avec une sécurité nulle.
private fun getNextWord() {
   ...
    } else {
        _currentScrambledWord.value = String(tempWord)
        _currentWordCount.value = (_currentWordCount.value)?.inc()
        wordsList.add(currentWord)
       }
   }
  1. Dans GameFragment, accédez à la valeur de score à l'aide de la propriété value. Dans la méthode showFinalScoreDialog(), remplacez viewModel.score par viewModel.score.value.
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score.value))
       ...
       .show()
}

Étape 2 : Associez des observateurs au score et au nombre de mots

Dans l'application, le score et le nombre de mots ne sont pas mis à jour. Vous allez les mettre à jour dans cette tâche à l'aide d'observateurs LiveData.

  1. Dans GameFragment, à l'intérieur de la méthode onViewCreated(), supprimez le code qui met à jour les affichages de texte du score et du nombre de mots.

Supprimez :

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. Dans GameFragment, à la fin de la méthode onViewCreated(), associez un observateur pour score. Transmettez viewLifecycleOwner en tant que premier paramètre à l'observateur et une expression lambda pour le deuxième paramètre. Dans l'expression lambda, transmettez le nouveau score en tant que paramètre et, dans le corps de la fonction, définissez le nouveau score sur l'affichage de texte.
viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })
  1. À la fin de la méthode onViewCreated(), associez un observateur pour currentWordCount LiveData. Transmettez viewLifecycleOwner en tant que premier paramètre à l'observateur et une expression lambda pour le deuxième paramètre. Dans l'expression lambda, transmettez le nouveau nombre de mots en tant que paramètre et, dans le corps de la fonction, définissez le nouveau nombre de mots avec MAX_NO_OF_WORDS sur l'affichage de texte.
viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })

Les nouveaux observateurs seront déclenchés lors de la modification de la valeur du score et du nombre de mots dans ViewModel, et ce, pendant toute la durée de vie du propriétaire du cycle de vie, à savoir GameFragment.

  1. Exécutez l'application pour voir la magie s'opérer. Essayez de deviner quelques mots. Le score et le nombre de mots sont également mis à jour correctement à l'écran. Notez que vous ne mettez pas à jour ces affichages de texte en fonction de certaines conditions du code. score et currentWordCount correspondent à LiveData, et les observateurs correspondants sont automatiquement appelés lorsque la valeur sous-jacente change.

80e118245bdde6df.png

7. Utiliser LiveData avec une liaison de données

Dans les tâches précédentes, votre application écoutait les modifications de données dans le code. De la même manière, les applications peuvent écouter les modifications de données en provenance de la mise en page. Avec la liaison de données, lorsqu'une valeur LiveData observable est modifiée, les éléments d'UI de la mise en page auxquels elle est liée en sont également informés, et l'UI peut être mise à jour à partir de la mise en page.

Concept : liaison de données

Dans les ateliers de programmation précédents, vous avez découvert View Binding (Liaison de vue), qui est une liaison unidirectionnelle. Vous pouvez lier des affichages au code, mais pas l'inverse.

Rappel pour la liaison de vue :

La liaison de vue est une fonctionnalité qui vous permet d'accéder plus facilement aux affichages dans le code. Elle génère une classe de liaison pour chaque fichier de mise en page XML. Une instance d'une classe de liaison contient des références directes à tous les affichages qui possèdent un ID dans la mise en page correspondante. Par exemple, l'application Unscramble utilise actuellement la fonctionnalité de liaison de vue, de sorte que les affichages puissent être référencés dans le code à l'aide de la classe de liaison générée.

Exemple :

binding.textViewUnscrambledWord.text = newWord
binding.score.text = getString(R.string.score, newScore)
binding.wordCount.text =
                  getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)

La fonctionnalité de liaison de vue ne vous permet pas de référencer les données de l'application dans les affichages (fichiers de mise en page). Pour ce faire, utilisez la liaison de données.

Liaison de données

La bibliothèque Data Binding fait également partie de la bibliothèque Android Jetpack. La liaison de données lie les composants de l'UI de vos mises en page aux sources de données de votre application à l'aide d'un format déclaratif, que nous étudierons dans la suite de cet atelier.

Pour faire simple, la liaison de données consiste à lier les données (à partir du code) aux vues et à lier les vues (liaison de vues au code) :

Exemple d'utilisation de la liaison de vue dans le contrôleur d'UI

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

Exemple d'utilisation de la liaison de données dans un fichier de mise en page

android:text="@{gameViewModel.currentScrambledWord}"

L'exemple ci-dessus montre comment utiliser la bibliothèque Data Binding pour affecter directement des données d'application aux affichages/widgets dans le fichier de mise en page. Notez l'utilisation de la syntaxe @{} dans l'expression d'affectation.

Le principal avantage de la liaison de données est de vous permettre de supprimer de nombreux appels de framework d'UI dans vos activités, ce qui les rend plus simples et plus faciles à gérer. Cela peut également améliorer les performances de votre application, et empêcher les fuites de mémoire et les exceptions de pointeur nul.

Étape 1 : Remplacez la liaison de vue par la liaison de données

  1. Dans le fichier build.gradle(Module), activez la propriété dataBinding dans la section buildFeatures.

Remplacez

buildFeatures {
   viewBinding = true
}

par

buildFeatures {
   dataBinding = true
}

Effectuez une synchronisation Gradle lorsque vous y êtes invité par Android Studio.

  1. Pour utiliser la liaison de données dans n'importe quel projet Kotlin, vous devez appliquer le plug-in kotlin-kapt. Cette étape a déjà été effectuée dans le fichier build.gradle(Module).
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

Les étapes ci-dessus génèrent automatiquement une classe de liaison pour chaque fichier XML de mise en page dans l'application. Si le nom du fichier de mise en page est activity_main.xml, votre classe de génération automatique s'appelle ActivityMainBinding.

Étape 2 : Convertissez le fichier de mise en page en mise en page de liaison de données

Les fichiers de mise en page de la liaison de données sont légèrement différents et commencent par la balise racine <layout>, suivie d'un élément <data> facultatif et d'un élément racine view. Cet élément de vue correspond à ce que serait votre élément racine dans un fichier de mise en page sans liaison.

  1. Ouvrez game_fragment.xml, puis sélectionnez l'onglet Code.
  2. Pour convertir la mise en page en une mise en page de liaison de données, encapsulez l'élément racine dans une balise <layout>. Vous devez également déplacer les définitions de l'espace de noms (attributs commençant par xmlns:) vers le nouvel élément racine. Ajoutez des balises <data></data> à l'intérieur de la balise <layout> située au-dessus de l'élément racine. Android Studio propose une méthode pratique pour effectuer cette opération automatiquement : effectuez un clic droit sur l'élément racine (ScrollView), sélectionnez Show Context Actions > Convert to data binding layout (Afficher les actions contextuelles > Convertir en mise en page de liaison de données).

8d48f58c2bdccb52.png

  1. Votre mise en page doit se présenter comme suit :
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <androidx.constraintlayout.widget.ConstraintLayout
         ...
       </androidx.constraintlayout.widget.ConstraintLayout>
   </ScrollView>
</layout>
  1. Dans GameFragment, au début de la méthode onCreateView(), modifiez l'instanciation de la variable binding pour qu'elle utilise la liaison de données.

Remplacez

binding = GameFragmentBinding.inflate(inflater, container, false)

par

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
  1. Compilez le code. Cela devrait se faire sans aucun problème. Votre application utilise à présent la liaison de données, et les vues de la mise en page peuvent accéder aux données de l'application.

8. Ajouter des variables de liaison de données

Dans cette tâche, vous allez ajouter des propriétés dans le fichier de mise en page afin d'accéder aux données de l'application à partir de viewModel. Vous allez initialiser les variables de mise en page dans le code.

  1. Dans game_fragment.xml, à l'intérieur de la balise <data>, ajoutez une balise enfant nommée <variable> et déclarez une propriété gameViewModel de type GameViewModel. Vous l'utiliserez pour lier les données de ViewModel à la mise en page.
<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

Notez que le type gameViewModel contient le nom du package. Assurez-vous que ce nom de package correspond à celui indiqué dans votre application.

  1. Sous la déclaration gameViewModel, ajoutez une autre variable de type Integer dans la balise <data> et nommez-la maxNoOfWords. Vous l'utiliserez pour créer une liaison avec la variable dans ViewModel afin de stocker le nombre de mots par jeu.
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. Dans GameFragment, au début de la méthode onViewCreated(), initialisez les variables de mise en page gameViewModel et maxNoOfWords.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. LiveData est une classe observable et sensible au cycle de vie. Vous devez donc transmettre le propriétaire du cycle de vie à la mise en page. Dans GameFragment, ajoutez le code suivant dans la méthode onViewCreated(), sous l'initialisation des variables de liaison.
   // Specify the fragment view as the lifecycle owner of the binding.
   // This is used so that the binding can observe LiveData updates
   binding.lifecycleOwner = viewLifecycleOwner

Pour rappel, vous avez mis en œuvre une fonctionnalité similaire lorsque vous avez implémenté les observateurs LiveData. Vous avez transmis viewLifecycleOwner en tant que paramètre aux observateurs LiveData.

9. Utiliser des expressions de liaison

Les expressions de liaison sont écrites dans la mise en page, dans les propriétés d'attribut (comme android:text) faisant référence aux propriétés de mise en page. Les propriétés de mise en page sont déclarées en haut du fichier de mise en page de la liaison de données, au moyen de la balise <variable>. Lorsque l'une des variables dépendantes change, la bibliothèque Data Binding exécute vos expressions de liaison (et met donc à jour les affichages). Cette détection des modifications constitue une excellente optimisation (que vous pouvez obtenir sans frais) lors de l'utilisation d'une bibliothèque Data Binding.

Syntaxe des expressions de liaison

Les expressions de liaison commencent par un symbole @ et sont encapsulées dans des accolades {}. Dans l'exemple suivant, le texte TextView est défini sur la propriété firstName de la variable user :

Exemple :

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

Étape 1 : Ajoutez une expression de liaison au mot actuel

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

  1. Dans game_fragment.xml, ajoutez un attribut text à l'affichage de texte textView_unscrambled_word. Utilisez la nouvelle variable de mise en page gameViewModel et affectez @{gameViewModel.currentScrambledWord} à l'attribut text.
<TextView
   android:id="@+id/textView_unscrambled_word"
   ...
   android:text="@{gameViewModel.currentScrambledWord}"
   .../>
  1. Dans GameFragment, supprimez le code d'observateur LiveData pour currentScrambledWord : vous n'avez plus besoin de ce code dans le fragment. La mise en page reçoit directement les mises à jour des modifications apportées à LiveData.

Supprimez :

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Exécutez votre application. Elle devrait fonctionner comme auparavant. À présent, l'affichage de texte du mot brouillé utilise les expressions de liaison pour mettre à jour l'UI, et non les observateurs LiveData.

Étape 2 : Ajoutez une expression de liaison au score et au nombre de mots

Ressources dans les expressions de liaison de données

Une expression de liaison de données peut référencer des ressources d'application avec la syntaxe suivante.

Exemple :

android:padding="@{@dimen/largePadding}"

Dans l'exemple ci-dessus, la valeur largePadding provenant du fichier de ressources dimen.xml est affectée à l'attribut padding.

Vous pouvez également transmettre des propriétés de mise en page en tant que paramètres de ressource.

Exemple :

android:text="@{@string/example_resource(user.lastName)}"

strings.xml

<string name="example_resource">Last Name: %s</string>

Dans l'exemple ci-dessus, example_resource est une ressource de chaîne avec l'espace réservé %s. Vous transmettez user.lastName en tant que paramètre de ressource dans l'expression de liaison, où user est une variable de mise en page.

Au cours de cette étape, vous allez ajouter des expressions de liaison aux affichages de texte du score et du nombre de mots, en transmettant les paramètres de ressource. Cette étape est semblable à celle que vous avez suivie pour textView_unscrambled_word ci-dessus.

  1. Dans game_fragment.xml, mettez à jour l'attribut text pour l'affichage de texte word_count avec l'expression de liaison suivante. Utilisez la ressource de chaîne word_count, et transmettez gameViewModel.currentWordCount et maxNoOfWords comme paramètres de ressource.
<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>
  1. Mettez à jour l'attribut text pour l'affichage de texte score avec l'expression de liaison suivante. Utilisez la ressource de chaîne score et transmettez gameViewModel.score comme paramètre de ressource.
<TextView
   android:id="@+id/score"
   ...
   android:text="@{@string/score(gameViewModel.score)}"
   ... />
  1. Supprimez les observateurs LiveData de GameFragment. Vous n'en avez plus besoin. Les expressions de liaison mettent à jour l'UI lorsque la classe LiveData correspondante change.

Supprimez :

viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })

viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })
  1. Exécutez l'application et essayez de deviner quelques mots. Votre code utilise à présent LiveData et des expressions de liaison pour mettre à jour l'UI.

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

Félicitations ! Vous savez maintenant comment utiliser LiveData avec des observateurs LiveData et LiveData avec des expressions de liaison.

10. Tester l'application Unscramble avec TalkBack activé

L'objectif, tout au long de ce cours, est de créer des applications accessibles au plus grand nombre d'utilisateurs possible. Certains utilisateurs peuvent utiliser Talkback pour accéder à votre appli et la parcourir. TalkBack est le lecteur d'écran de Google intégré aux appareils Android. Cette fonctionnalité est destinée à fournir des commentaires audio pour vous aider dans l'utilisation de votre appareil sans regarder l'écran.

Assurez-vous qu'il est possible de jouer à ce jeu lorsque TalkBack est activé.

  1. Activez TalkBack sur votre appareil en suivant ces instructions.
  2. Revenez à l'application Unscramble.
  3. Explorez votre application avec TalkBack en suivant ces instructions. Balayez l'écran vers la droite pour parcourir les éléments de l'écran dans l'ordre, puis vers la gauche pour procéder dans la direction opposée. Appuyez deux fois n'importe où pour effectuer une sélection. Vérifiez que vous pouvez accéder à tous les éléments de votre application à l'aide des gestes de balayage.
  4. Assurez-vous qu'un utilisateur de TalkBack peut accéder à chaque élément affiché à l'écran.
  5. Comme vous pouvez le constater, TalkBack essaie de lire le mot brouillé comme s'il s'agissait d'un mot. Cela peut prêter à confusion pour le joueur, car ce n'est pas un mot du dictionnaire.
  6. L'idéal serait que TalkBack lise à voix haute chaque caractère qui compose le mot brouillé. Dans l'élément GameViewModel, convertissez le mot brouillé String en une chaîne Spannable. Une chaîne Spannable est une chaîne à laquelle sont associées des informations supplémentaires. Dans le cas présent, nous voulons associer la chaîne à un élément TtsSpan TYPE_VERBATIM afin que le moteur de synthèse vocale lise à voix haute le mot brouillé, caractère par caractère.
  7. Dans GameViewModel, utilisez le code suivant pour modifier la méthode de déclaration de la variable currentScrambledWord :
val currentScrambledWord: LiveData<Spannable> = Transformations.map(_currentScrambledWord) {
    if (it == null) {
        SpannableString("")
    } else {
        val scrambledWord = it.toString()
        val spannable: Spannable = SpannableString(scrambledWord)
        spannable.setSpan(
            TtsSpan.VerbatimBuilder(scrambledWord).build(),
            0,
            scrambledWord.length,
            Spannable.SPAN_INCLUSIVE_INCLUSIVE
        )
        spannable
    }
}

Cette variable est maintenant LiveData<Spannable> au lieu de LiveData<String>. Vous n'avez pas à comprendre tous les mécanismes de fonctionnement. Sachez simplement que l'implémentation utilise une transformation LiveData pour convertir le mot brouillé actuel String en une chaîne Spannable pouvant être gérée correctement par le service d'accessibilité. Dans l'atelier de programmation suivant, vous en apprendrez davantage sur les transformations LiveData. Celles-ci vous permettent de renvoyer une instance LiveData différente en fonction de la valeur de la classe LiveData correspondante.

  1. Exécutez l'application Unscramble, puis explorez-la avec TalkBack. TalkBack devrait maintenant lire à voix haute chaque caractère du mot brouillé.

Consultez ces principes pour savoir comment améliorer l'accessibilité de votre application.

11. Supprimer le code inutilisé

Il est recommandé de supprimer tout code inutile, non utilisé ou indésirable pour la solution. Non seulement cela facilite la gestion du code, mais cela permet aussi aux nouveaux collaborateurs de mieux le comprendre.

  1. Dans GameFragment, supprimez les méthodes getNextScrambledWord() et onDetach().
  2. Dans GameViewModel, supprimez la méthode onCleared().
  3. Supprimez toutes les importations inutilisées en haut des fichiers sources. Elles apparaissent en grisé.

Vous n'avez plus besoin des instructions de journalisation. Vous pouvez donc les supprimer du code si vous le souhaitez.

  1. [Facultatif] Dans les fichiers sources (GameFragment.kt et GameViewModel.kt), supprimez les instructions Log que vous avez ajoutées dans l'atelier de programmation précédent afin de comprendre le cycle de vie de ViewModel.

12. Code de solution

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

  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.

13. Résumé

  • LiveData contient des données ; LiveData est un wrapper qui peut être utilisé avec n'importe quelle donnée.
  • LiveData est une classe observable, ce qui signifie qu'un observateur est averti en cas de modification des données détenues par l'objet LiveData.
  • LiveData est sensible au cycle de vie. Lorsque vous associez un observateur à LiveData, il est associé à un LifecycleOwner (il s'agit généralement d'une activité ou d'un fragment). La classe LiveData ne met à jour que les observateurs dont l'état de cycle de vie est actif, tels que STARTED ou RESUMED. Pour en savoir plus sur LiveData et sur l'observation, cliquez ici.
  • Les applications peuvent écouter les modifications de la classe LiveData en provenance de la mise en page à l'aide de la liaison de données et d'expressions de liaison.
  • Les expressions de liaison sont écrites dans la mise en page, dans les propriétés d'attribut (comme android:text) faisant référence aux propriétés de mise en page.

14. En savoir plus

Articles de blog