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.
Écran titre | Écran de jeu | Écran de score |
Introduction
Dans cet atelier de programmation, vous allez découvrir l'un des composants d'architecture Android, ViewModel
:
- Vous utilisez la classe
ViewModel
pour stocker et gérer les données liées à l'UI tout en tenant compte du cycle de vie. La classeViewModel
permet aux données 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 utilisez la classe
ViewModelFactory
pour instancier et renvoyer l'objetViewModel
qui survit aux modifications de configuration.
Ce que vous devez déjà savoir
- Vous savez comment créer des applications Android de base en langage Kotlin.
- Comment utiliser le graphique de navigation pour implémenter la navigation dans votre application.
- Ajouter du code pour naviguer entre les destinations de votre application et transmettre des données entre les destinations de navigation.
- Vous comprenez le fonctionnement des cycles de vie des activités et des fragments.
- Vous savez ajouter des informations de journalisation à une application et lire des journaux à l'aide de Logcat dans Android Studio.
Points abordés
- Utiliser l'architecture d'application Android recommandée.
- Utiliser les classes
Lifecycle
,ViewModel
etViewModelFactory
dans votre application - Vous savez comment conserver les données de l'UI en modifiant la configuration de l'appareil.
- Définition et utilisation du modèle de conception de méthode de fabrique
- Comment créer un objet
ViewModel
à l'aide de l'interfaceViewModelProvider.Factory
.
Objectifs de l'atelier
- Ajoutez un
ViewModel
à l'application pour enregistrer ses données et les conserver en cas de modification de la configuration. - Utilisez
ViewModelFactory
et le modèle de conception de méthode de fabrique pour instancier un objetViewModel
avec des paramètres de constructeur.
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 cette tâche, vous allez télécharger et exécuter l'application de démarrage, puis examiner le code.
Étape 1 : Premiers pas
- Téléchargez le code de démarrage de GuessTheWord et ouvrez le projet dans Android Studio.
- Exécutez l'application sur un appareil Android ou sur un émulateur.
- Appuyez sur les boutons. Notez que le bouton Ignorer affiche le mot suivant et diminue le score de un, tandis que le bouton OK affiche le mot suivant et augmente le score de un. Le bouton End Game (Terminer la partie) n'est pas implémenté. Par conséquent, rien ne se passe lorsque vous appuyez dessus.
Étape 2 : Parcourez le code
- Dans Android Studio, explorez le code pour comprendre le fonctionnement de l'application.
- Assurez-vous de consulter les fichiers décrits ci-dessous, qui sont particulièrement importants.
MainActivity.kt
Ce fichier ne contient que du code par défaut généré à partir du modèle.
res/layout/main_activity.xml
Ce fichier contient la mise en page principale de l'application. NavHostFragment
héberge les autres fragments lorsque l'utilisateur parcourt l'application.
Fragments d'UI
Le code de démarrage comporte trois fragments dans trois packages différents sous le package com.example.android.guesstheword.screens
:
title/TitleFragment
pour l'écran de titregame/GameFragment
pour l'écran de jeuscore/ScoreFragment
pour l'écran de score
screens/title/TitleFragment.kt
Le fragment de titre est le premier écran qui s'affiche au lancement de l'application. Un gestionnaire de clics est défini sur le bouton Play (Jouer) pour accéder à l'écran de jeu.
screens/game/GameFragment.kt
Il s'agit du fragment principal, dans lequel la plupart des actions du jeu se déroulent :
- Des variables sont définies pour le mot actuel et le score actuel.
- Le
wordList
défini dans la méthoderesetList()
est un exemple de liste de mots à utiliser dans le jeu. - La méthode
onSkip()
est le gestionnaire de clics du bouton Ignorer. Il diminue le score de 1, puis affiche le mot suivant à l'aide de la méthodenextWord()
. - La méthode
onCorrect()
est le gestionnaire de clics du bouton Got It (J'ai compris). Cette méthode est implémentée de la même manière que la méthodeonSkip()
. La seule différence est que cette méthode ajoute 1 au score au lieu de le soustraire.
screens/score/ScoreFragment.kt
ScoreFragment
est le dernier écran du jeu. Il affiche le score final du joueur. Dans cet atelier de programmation, vous allez ajouter l'implémentation pour afficher cet écran et le score final.
res/navigation/main_navigation.xml
Le graphique de navigation montre comment les fragments sont connectés par la navigation :
- À partir du fragment de titre, l'utilisateur peut accéder au fragment de jeu.
- Depuis le fragment de jeu, l'utilisateur peut accéder au fragment de score.
- À partir du fragment de score, l'utilisateur peut revenir au fragment de jeu.
Dans cette tâche, vous allez identifier les problèmes de l'application de démarrage GuessTheWord.
- Exécutez le code de démarrage et jouez avec quelques mots en appuyant sur Passer ou OK après chaque mot.
- L'écran du jeu affiche désormais un mot et le score actuel. Modifiez l'orientation de l'écran en faisant pivoter l'appareil (si vous utilisez un appareil physique) ou en effectuant l'opération correspondante sur l'émulateur. Notez que le score actuel est perdu.
- Jouez avec quelques mots supplémentaires. Lorsque l'écran du jeu s'affiche avec un score, fermez l'application, puis rouvrez-la. Vous remarquerez que le jeu redémarre depuis le début, car l'état de l'application n'est pas enregistré.
- Jouez avec quelques mots, puis appuyez sur le bouton Terminer la partie. Vous remarquerez qu'il ne se passe rien.
Problèmes dans l'application :
- L'application de démarrage n'enregistre ni ne restaure l'état de l'application lorsque la configuration est modifiée, par exemple lorsque l'orientation de l'appareil change, ou lorsque l'application s'arrête et redémarre.
Vous pouvez résoudre ce problème à l'aide du rappelonSaveInstanceState()
. Toutefois, pour utiliser la méthodeonSaveInstanceState()
, vous devez écrire du code supplémentaire afin d'enregistrer l'état d'un groupe, mais aussi pour implémenter une logique permettant de récupérer cet état. De plus, la quantité de données pouvant être stockée est particulièrement réduite. - L'écran de jeu ne redirige pas vers l'écran de score lorsque l'utilisateur appuie sur le bouton Fin de partie.
Vous pouvez résoudre ces problèmes à l'aide des composants d'architecture d'application que vous avez découverts dans cet atelier de programmation.
Architecture de l'application
L'architecture d'application est une façon de concevoir les classes de vos applications et les relations entre elles, de sorte que le code soit organisé, fonctionne bien dans des scénarios particuliers et soit facile à utiliser. Dans cet ensemble de quatre ateliers de programmation, les améliorations que vous apportez à l'application GuessTheWord suivent les consignes de l'architecture des applications Android et utilisent les composants d'architecture Android. L'architecture de l'application Android est semblable au modèle architectural MVVM (Model-View-ViewModel).
L'application GuessTheWord suit le principe de conception de la séparation des tâches et est divisée en classes, chacune traitant d'un problème distinct. Dans ce premier atelier de programmation de la leçon, les classes avec lesquelles vous allez travailler sont un contrôleur d'UI, un ViewModel
et un ViewModelFactory
.
Contrôleur d'interface utilisateur
Un contrôleur d'UI est une classe basée sur l'UI, comme Activity
ou Fragment
. Un contrôleur d'UI ne doit contenir que la logique qui gère les interactions entre l'UI et le système d'exploitation, comme l'affichage des vues et la capture des saisies utilisateur. Ne placez pas de logique de prise de décision, comme la logique qui détermine le texte à afficher, dans le contrôleur d'UI.
Dans le code de démarrage de GuessTheWord, les contrôleurs d'UI sont les trois fragments : GameFragment
, ScoreFragment,
et TitleFragment
. En suivant le principe de conception de la "séparation des préoccupations", GameFragment
n'est responsable que du dessin des éléments du jeu à l'écran et de la détection des clics de l'utilisateur sur les boutons, et rien de plus. Lorsque l'utilisateur appuie sur un bouton, ces informations sont transmises à GameViewModel
.
ViewModel
Un ViewModel
contient les données à afficher dans un fragment ou une activité associés au ViewModel
. Un ViewModel
peut effectuer des calculs et des transformations simples sur les données afin que le contrôleur d'interface utilisateur puisse les afficher. Dans cette architecture, ViewModel
prend les décisions.GameViewModel
contient des données telles que la valeur du score, la liste des mots et le mot actuel, car il s'agit des données à afficher à l'écran. Le GameViewModel
contient également la logique métier permettant d'effectuer des calculs simples pour déterminer l'état actuel des données.
ViewModelFactory
Un ViewModelFactory
instancie des objets ViewModel
, avec ou sans paramètres de constructeur.
Dans les prochains ateliers de programmation, vous découvrirez d'autres composants d'architecture Android liés aux contrôleurs d'UI et à ViewModel
.
La classe ViewModel
est conçue pour stocker et gérer les données liées à l'UI. Dans cette application, chaque ViewModel
est associé à un fragment.
Dans cette tâche, vous allez ajouter votre premier ViewModel
à votre application, le GameViewModel
pour le GameFragment
. Vous découvrirez également ce que signifie le fait que ViewModel
est sensible au cycle de vie.
Étape 1 : Ajoutez la classe GameViewModel
- Ouvrez le fichier
build.gradle(module:app)
. Dans le blocdependencies
, ajoutez la dépendance Gradle pourViewModel
.
Si vous utilisez la dernière version de la bibliothèque, l'application de solution devrait se compiler comme prévu. Si ce n'est pas le cas, essayez de résoudre le problème ou revenez à la version indiquée ci-dessous.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Dans le dossier du package
screens/game/
, créez une classe Kotlin appeléeGameViewModel
. - Configurez la classe
GameViewModel
pour qu'elle étende la classe abstraiteViewModel
. - Pour vous aider à mieux comprendre comment
ViewModel
est compatible avec le cycle de vie, ajoutez un blocinit
avec une instructionlog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Étape 2 : Remplacez onCleared() et ajoutez une journalisation
Le ViewModel
est détruit lorsque le fragment associé est dissocié ou lorsque l'activité se termine. Juste avant la destruction du ViewModel
, le rappel onCleared()
est utilisé pour nettoyer les ressources.
- Dans la classe
GameViewModel
, remplacez la méthodeonCleared()
. - Ajoutez une instruction de journalisation dans
onCleared()
pour suivre le cycle de vie deGameViewModel
.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
Étape 3 : Associez GameViewModel au fragment de jeu
Un ViewModel
doit être associé à un contrôleur d'UI. Pour les associer, vous devez créer une référence au ViewModel
dans le contrôleur d'UI.
Au cours de cette étape, vous allez créer une référence de GameViewModel
dans le contrôleur d'UI correspondant, soit GameFragment
.
- Dans la classe
GameFragment
, ajoutez un champ de typeGameViewModel
au niveau supérieur en tant que variable de classe.
private lateinit var viewModel: GameViewModel
Étape 4 : Initialiser le ViewModel
Lors des changements de configuration, comme la rotation de l'écran, les contrôleurs d'UI tels que les fragments sont recréés. Toutefois, les instances ViewModel
survivent. Si vous créez l'instance ViewModel
à l'aide de la classe ViewModel
, un nouvel objet est créé chaque fois que le fragment est recréé. Créez plutôt l'instance ViewModel
à l'aide d'un ViewModelProvider
.
Fonctionnement de ViewModelProvider
:
ViewModelProvider
renvoie unViewModel
existant, le cas échéant, ou en crée un s'il n'existe pas encore.ViewModelProvider
crée une instanceViewModel
en association avec la portée donnée (une activité ou un fragment).- Le
ViewModel
créé est conservé tant que le champ d'application est actif. Par exemple, si le champ d'application est un fragment, leViewModel
est conservé jusqu'à ce que le fragment soit dissocié.
Initialisez ViewModel
à l'aide de la méthode ViewModelProviders.of()
pour créer un ViewModelProvider
:
- Dans la classe
GameFragment
, initialisez la variableviewModel
. Insérez ce code dansonCreateView()
, après la définition de la variable de liaison. Utilisez la méthodeViewModelProviders.of()
et transmettez le contexteGameFragment
associé et la classeGameViewModel
. - Au-dessus de l'initialisation de l'objet
ViewModel
, ajoutez une instruction de journalisation pour enregistrer l'appel de la méthodeViewModelProviders.of()
.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- Exécutez l'application. Dans Android Studio, ouvrez le volet Logcat et filtrez sur
Game
. Appuyez sur le bouton Play (Lecture) sur votre appareil ou votre émulateur. L'écran du jeu s'ouvre.
Comme indiqué dans Logcat, la méthodeonCreateView()
deGameFragment
appelle la méthodeViewModelProviders.of()
pour créerGameViewModel
. Les instructions de journalisation que vous avez ajoutées àGameFragment
etGameViewModel
s'affichent dans Logcat.
- Activez la rotation automatique sur votre appareil ou votre émulateur, puis modifiez l'orientation de l'écran plusieurs fois.
GameFragment
est détruit et recréé à chaque fois, doncViewModelProviders.of()
est appelé à chaque fois. MaisGameViewModel
n'est créé qu'une seule fois, et il n'est pas recréé ni détruit à chaque appel.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Quittez le jeu ou sortez du fragment de jeu.
GameFragment
est également détruit. LeGameViewModel
associé est également détruit, et le rappelonCleared()
est appelé.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
Le ViewModel
survit aux modifications de configuration. Il est donc idéal pour les données qui doivent survivre à ces modifications :
- Placez les données à afficher à l'écran et le code permettant de traiter ces données dans
ViewModel
. - Le
ViewModel
ne doit jamais contenir de références à des fragments, des activités ou des vues, car les activités, les fragments et les vues ne survivent pas aux modifications de configuration.
À titre de comparaison, voici comment les données de l'UI GameFragment
sont gérées dans l'application de démarrage avant et après l'ajout de ViewModel
:ViewModel
- Avant d'ajouter
ViewModel
:
Lorsque l'application subit un changement de configuration, comme une rotation de l'écran, le fragment de jeu est détruit et recréé. Les données sont perdues. - Après avoir ajouté
ViewModel
et déplacé les données d'UI du fragment de jeu dansViewModel
:
Toutes les données dont le fragment a besoin pour s'afficher se trouvent désormais dansViewModel
. Lorsque la configuration de l'application est modifiée,ViewModel
survit et les données sont conservées.
Dans cette tâche, vous allez déplacer les données de l'UI de l'application dans la classe GameViewModel
, ainsi que les méthodes de traitement des données. Cela permet de conserver les données lors des modifications de configuration.
Étape 1 : Déplacez les champs de données et le traitement des données vers le ViewModel
Déplacez les champs de données et les méthodes suivants de GameFragment
vers GameViewModel
:
- Déplacez les champs de données
word
,score
etwordList
. Assurez-vous queword
etscore
ne sont pasprivate
.
Ne déplacez pas la variable de liaison,GameFragmentBinding
, car elle contient des références aux vues. Cette variable est utilisée pour développer la mise en page, configurer les écouteurs de clics et afficher les données à l'écran, qui sont les responsabilités du fragment. - Déplacez les méthodes
resetList()
etnextWord()
. Ces méthodes déterminent le mot à afficher à l'écran. - Dans la méthode
onCreateView()
, déplacez les appels de méthode versresetList()
etnextWord()
vers le blocinit
deGameViewModel
.
Ces méthodes doivent se trouver dans le blocinit
, car vous devez réinitialiser la liste de mots lorsque leViewModel
est créé, et non à chaque fois que le fragment est créé. Vous pouvez supprimer l'instruction de journalisation dans le blocinit
deGameFragment
.
Les gestionnaires de clics onSkip()
et onCorrect()
de GameFragment
contiennent du code permettant de traiter les données et de mettre à jour l'UI. Le code permettant de mettre à jour l'UI doit rester dans le fragment, mais le code permettant de traiter les données doit être déplacé vers ViewModel
.
Pour l'instant, placez les méthodes identiques aux deux endroits :
- Copiez les méthodes
onSkip()
etonCorrect()
deGameFragment
versGameViewModel
. - Dans
GameViewModel
, assurez-vous que les méthodesonSkip()
etonCorrect()
ne sont pasprivate
, car vous ferez référence à ces méthodes à partir du fragment.
Voici le code de la classe GameViewModel
après refactoring :
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
Voici le code de la classe GameFragment
après refactoring :
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
Étape 2 : Mettez à jour les références aux gestionnaires de clics et aux champs de données dans GameFragment
- Dans
GameFragment
, mettez à jour les méthodesonSkip()
etonCorrect()
. Supprimez le code permettant de mettre à jour le score, puis appelez les méthodesonSkip()
etonCorrect()
correspondantes surviewModel
. - Étant donné que vous avez déplacé la méthode
nextWord()
versViewModel
, le fragment de jeu ne peut plus y accéder.
DansGameFragment
, dans les méthodesonSkip()
etonCorrect()
, remplacez l'appel ànextWord()
parupdateScoreText()
etupdateWordText()
. Ces méthodes affichent les données à l'écran.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- Dans
GameFragment
, mettez à jour les variablesscore
etword
pour utiliser les variablesGameViewModel
, car ces variables se trouvent désormais dansGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- Dans
GameViewModel
, à l'intérieur de la méthodenextWord()
, supprimez les appels aux méthodesupdateWordText()
etupdateScoreText()
. Ces méthodes sont désormais appelées à partir deGameFragment
. - Compilez l'application et assurez-vous qu'elle ne comporte aucune erreur. Si vous rencontrez des erreurs, nettoyez et recompilez le projet.
- Exécutez l'application et essayez de deviner quelques mots. Faites pivoter l'appareil lorsque vous êtes sur l'écran du jeu. Notez que le score et le mot actuels sont conservés après le changement d'orientation.
Bravo ! Désormais, toutes les données de votre application sont stockées dans un ViewModel
. Elles sont donc conservées lors des modifications de configuration.
Dans cette tâche, vous allez implémenter l'écouteur de clics pour le bouton End Game (Terminer la partie).
- Dans
GameFragment
, ajoutez une méthode appeléeonEndGame()
. La méthodeonEndGame()
sera appelée lorsque l'utilisateur appuiera sur le bouton End Game (Fin de partie).
private fun onEndGame() {
}
- Dans
GameFragment
, à l'intérieur de la méthodeonCreateView()
, recherchez le code qui définit les écouteurs de clics pour les boutons Got It (OK) et Skip (Ignorer). Juste en dessous de ces deux lignes, définissez un écouteur de clics pour le bouton End Game (Fin de partie). Utilisez la variable de liaisonbinding
. Dans l'écouteur de clics, appelez la méthodeonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- Dans
GameFragment
, ajoutez une méthode appeléegameFinished()
pour accéder à l'écran du score dans l'application. Transmettez le score en tant qu'argument à l'aide de Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- Dans la méthode
onEndGame()
, appelez la méthodegameFinished()
.
private fun onEndGame() {
gameFinished()
}
- Exécutez l'application, jouez et parcourez quelques mots. Appuyez sur le bouton End Game (Terminer la partie). Notez que l'application accède à l'écran du score, mais que le score final n'est pas affiché. Vous résoudrez ce problème dans la prochaine tâche.
Lorsque l'utilisateur termine le jeu, ScoreFragment
n'affiche pas le score. Vous souhaitez qu'un ViewModel
contienne le score à afficher par le ScoreFragment
. Vous transmettrez la valeur du score lors de l'initialisation de ViewModel
à l'aide du modèle de méthode de fabrique.
Le modèle de méthode factory est un modèle de conception de création qui utilise des méthodes factory pour créer des objets. Une méthode de fabrique est une méthode qui renvoie une instance de la même classe.
Dans cette tâche, vous allez créer un ViewModel
avec un constructeur paramétré pour le fragment de score et une méthode de fabrique pour instancier le ViewModel
.
- Sous le package
score
, créez une classe Kotlin appeléeScoreViewModel
. Cette classe sera leViewModel
du fragment de score. - Étendez la classe
ScoreViewModel
à partir deViewModel.
. Ajoutez un paramètre de constructeur pour le score final. Ajoutez un blocinit
avec une instruction de journalisation. - Dans la classe
ScoreViewModel
, ajoutez une variable appeléescore
pour enregistrer le score final.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- Sous le package
score
, créez une autre classe Kotlin appeléeScoreViewModelFactory
. Cette classe sera chargée d'instancier l'objetScoreViewModel
. - Étendez la classe
ScoreViewModelFactory
depuisViewModelProvider.Factory
. Ajoutez un paramètre de constructeur pour le score final.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- Dans
ScoreViewModelFactory
, Android Studio affiche une erreur concernant un membre abstrait non implémenté. Pour résoudre l'erreur, remplacez la méthodecreate()
. Dans la méthodecreate()
, renvoyez l'objetScoreViewModel
nouvellement construit.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- Dans
ScoreFragment
, créez des variables de classe pourScoreViewModel
etScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- Dans
ScoreFragment
, dansonCreateView()
, après avoir initialisé la variablebinding
, initialisezviewModelFactory
. UtilisezScoreViewModelFactory
. Transmettez le score final du bundle d'arguments en tant que paramètre de constructeur àScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- Dans
onCreateView(
, après avoir initialiséviewModelFactory
, initialisez l'objetviewModel
. Appelez la méthodeViewModelProviders.of()
, transmettez le contexte du fragment de score associé etviewModelFactory
. Cela créera l'objetScoreViewModel
à l'aide de la méthode de fabrique définie dans la classeviewModelFactory
.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- Dans la méthode
onCreateView()
, après avoir initialiséviewModel
, définissez le texte de la vuescoreText
sur le score final défini dansScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Exécutez votre application et jouez. Parcourez certains ou tous les mots, puis appuyez sur Terminer la partie. Notez que le fragment de score affiche désormais le score final.
- Facultatif : Vérifiez les journaux
ScoreViewModel
dans Logcat en filtrant surScoreViewModel
. La valeur du score doit s'afficher.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
Dans cette tâche, vous avez implémenté ScoreFragment
pour utiliser ViewModel
. Vous avez également appris à créer un constructeur paramétré pour un ViewModel
à l'aide de l'interface ViewModelFactory
.
Félicitations ! Vous avez modifié l'architecture de votre application pour utiliser l'un des composants d'architecture Android, ViewModel
. Vous avez résolu le problème de cycle de vie de l'application, et les données du jeu survivent désormais aux modifications de configuration. Vous avez également appris à créer un constructeur paramétré pour créer un ViewModel
à l'aide de l'interface ViewModelFactory
.
Projet Android Studio : GuessTheWord
- Les principes fondamentaux de l'architecture des applications Android recommandent de séparer les classes ayant des responsabilités distinctes.
- Un contrôleur d'UI est une classe basée sur l'UI, comme
Activity
ouFragment
. Les contrôleurs d'UI ne doivent contenir que la logique qui gère les interactions entre l'UI et le système d'exploitation. Ils ne doivent pas contenir de données à afficher dans l'UI. Placez ces données dans unViewModel
. - La classe
ViewModel
stocke et gère les données liées à l'UI. La classeViewModel
permet aux données de survivre à des modifications de la configuration telles que les rotations d'écran. ViewModel
est l'un des composants d'architecture Android recommandés.ViewModelProvider.Factory
est une interface que vous pouvez utiliser pour créer un objetViewModel
.
Le tableau ci-dessous compare les contrôleurs d'UI avec les instances ViewModel
qui contiennent leurs données :
Contrôleur d'UI | ViewModel |
Le | Un exemple de |
Ne contient aucune donnée à afficher dans l'UI. | Contient les données que le contrôleur d'UI affiche dans l'UI. |
Contient le code permettant d'afficher les données et le code d'événement utilisateur, tel que les écouteurs de clic. | Contient le code de traitement des données. |
Détruit et recréé à chaque modification de la configuration. | Détruit uniquement lorsque le contrôleur d'UI associé disparaît définitivement (pour une activité, lorsque l'activité se termine, ou pour un fragment, lorsque le fragment est dissocié). |
Contient des vues. | Ne doit jamais contenir de références à des activités, des fragments ou des vues, car ils ne survivent pas aux modifications de configuration, contrairement à |
Contient une référence à l' | Ne contient aucune référence au contrôleur d'UI associé. |
Cours Udacity :
Documentation pour les développeurs Android :
- Présentation de ViewModel
- Gérer les cycles de vie à l'aide de composants tenant compte des cycles de vie
- Guide de l'architecture des applications
ViewModelProvider
ViewModelProvider.Factory
Autre :
- Le modèle architectural MVVM (Model-View-ViewModel).
- Principe de conception de la séparation des tâches
- Modèle de méthode factory
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
Dans quelle classe devez-vous enregistrer les données de l'application pour éviter de perdre des données lorsque vous modifiez la configuration d'un appareil ?
ViewModel
LiveData
Fragment
Activity
Question 2
Un ViewModel
ne doit jamais contenir de références à des fragments, des activités ou des vues. Vrai ou faux ?
- Vrai
- Faux
Question 3
À quel moment un ViewModel
est-il détruit ?
- Lorsque le contrôleur d'interface utilisateur associé est détruit et recréé lors d'un changement d'orientation de l'appareil.
- Lors d'un changement d'orientation.
- Lorsque le contrôleur d'interface utilisateur associé est terminé (s'il s'agit d'une activité) ou dissocié (s'il s'agit d'un fragment).
- Lorsque l'utilisateur appuie sur le bouton "Retour".
Question 4
À quoi sert l'interface ViewModelFactory
?
- Instanciation d'un objet
ViewModel
. - Conserver les données lors des changements d'orientation.
- Actualiser les données affichées à l'écran.
- Recevoir des notifications lorsque les données de l'application sont modifiées.
Passez à la leçon suivante :
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.