Cet atelier de programmation fait partie du cours Android Kotlin Fundamentals. Vous tirerez le meilleur parti de ce cours si vous suivez les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont répertoriés sur la page de destination des ateliers de programmation Android Kotlin Fundamentals.
É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
:
- La classe
ViewModel
vous permet de stocker et de gérer les données liées à l'interface utilisateur en tenant compte de la notion de 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 apportées à 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
- Créer des applications Android de base en Kotlin
- Utiliser le graphique de navigation pour mettre en œuvre la navigation dans votre application
- Comment ajouter du code pour naviguer entre les destinations de votre application et transmettre des données entre les destinations de navigation
- Fonctionnement des cycles de vie des activités et des fragments
- Ajouter des informations de journalisation à une application et lire les journaux à l'aide de Logcat dans Android Studio
Points abordés
- Utiliser l'architecture des applications Android recommandée
- Utiliser les classes
Lifecycle
,ViewModel
etViewModelFactory
dans votre application - Conserver les données de l'interface utilisateur en modifiant la configuration de l'appareil
- Présentation de la méthode de méthode d'usine et de son utilisation
- Créer un objet
ViewModel
à l'aide de l'interfaceViewModelProvider.Factory
Objectifs de l'atelier
- Ajoutez un
ViewModel
à l'application pour enregistrer les données de l'application et les conserver après les modifications de configuration. - Utilisez
ViewModelFactory
et le modèle de conception avec la méthode par défaut pour instancier un objetViewModel
avec des paramètres de constructeur.
Dans les ateliers de programmation de la leçon 5, vous allez développer l'application GuessTheWord en commençant par le code de démarrage. GuessTheWord est un jeu de charades à deux joueurs, où les joueurs collaborent pour obtenir le meilleur score possible.
Le premier joueur analyse les mots dans l'application et les joue tous à tour de rôle. Veillez donc à ne pas afficher le mot au deuxième joueur. Le deuxième joueur essaie de deviner le mot.
Pour lancer ce jeu, le premier joueur ouvre l'application sur l'appareil et voit un mot, par exemple "guitare", comme illustré dans la capture d'écran ci-dessous.
Le premier joueur joue le mot, en prenant soin de ne pas le prononcer.
- Lorsque le deuxième joueur essaie de deviner le mot, le premier appuie sur le bouton OK, ce qui augmente le nombre de points et affiche le mot suivant.
- S'il ne parvient pas à deviner le mot, le premier joueur appuie sur le bouton Ignorer, ce qui diminue le nombre de fois jusqu'à l'affichage du mot suivant.
- Pour terminer la partie, appuyez sur le bouton Terminer le jeu. (Cette fonctionnalité n'est pas disponible dans le code de démarrage du premier atelier de programmation de cette série.)
Dans cette tâche, vous allez télécharger et exécuter l'application de départ, puis examiner le code.
Étape 1: Premiers pas
- Téléchargez le code de démarrage GuessTheWord, puis ouvrez le projet dans Android Studio.
- Exécutez l'application sur un appareil Android ou un émulateur.
- Appuyez sur les boutons. Notez que le bouton Ignorer affiche le mot suivant et diminue le score de 1. Le bouton OK permet d'afficher le mot suivant et d'augmenter le score de 1. Le bouton Mettre fin au jeu n'est pas implémenté. Si vous appuyez dessus, rien ne se passe.
Étape 2: Tutoriel pour le code
- Dans Android Studio, explorez le code pour vous faire une idée du fonctionnement de l'application.
- Veillez à consulter les fichiers décrits ci-dessous, particulièrement importants.
MainActivity.kt
Ce fichier ne contient que du code par défaut généré par un modèle.
res/layout/main_activity.xml
Ce fichier contient la mise en page principale de l'application. Le NavHostFragment
héberge les autres fragments lorsque l'utilisateur navigue dans l'application.
Fragments d'interface utilisateur
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
screen/title/TitreFragment.kt
Le fragment de titre est le premier écran affiché lorsque l'application est lancée. Un gestionnaire de clics est défini sur le bouton Jouer pour accéder à l'écran du jeu.
screen/game/GameFragment.kt
Principal fragment, dans lequel la plupart des actions du jeu ont lieu:
- Les variables sont définies pour le mot actuel et le score actuel.
- La
wordList
définie 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 de 1, puis affiche le mot suivant à l'aide de la méthodenextWord()
. - La méthode
onCorrect()
correspond au gestionnaire de clics du bouton Got It (OK). 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 soustraire.
écrans/score/FragmentFragment.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 afficher le score final.
res/navigation/main_navigation.xml
Le graphique de navigation montre comment les fragments sont connectés via la navigation:
- L'utilisateur peut accéder au fragment de jeu à partir du fragment de titre.
- Depuis le fragment de jeu, l'utilisateur peut accéder au fragment de score.
- Depuis le fragment de score, l'utilisateur peut revenir au fragment de jeu.
Dans cette tâche, vous allez trouver des problèmes avec l'application de départ GuessTheWord.
- Exécutez le code de démarrage et jouez en 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 ou l'émulateur. Vous remarquerez que le score actuel est perdu.
- Exécutez le jeu avec quelques mots supplémentaires. Lorsque l'écran du jeu s'affiche, fermez l'application, puis rouvrez-la. Notez que le jeu redémarre depuis le début, car l'état de l'application n'est pas enregistré.
- Jouez en quelques mots, puis appuyez sur le bouton End Game (Terminer le jeu). Notez qu'il ne se passe rien.
Problèmes dans l'application:
- L'application de départ n'enregistre pas et ne restaure l'état d'une application lors de changements de configuration, 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()
. Cependant, l'utilisation de la méthodeonSaveInstanceState()
nécessite d'écrire du code supplémentaire pour enregistrer l'état dans un groupe et de mettre en œuvre la logique permettant de récupérer cet état. De plus, la quantité de données pouvant être stockées est minimale. - L'écran de jeu ne s'affiche pas lorsque l'utilisateur appuie sur le bouton End Game (Terminer le jeu).
Vous pouvez résoudre ces problèmes à l'aide des composants de l'architecture de l'application que vous avez découverts dans cet atelier de programmation.
Architecture de l'application
L'architecture des applications est un moyen de concevoir vos applications et leurs classes, ainsi que les relations entre elles, de sorte que le code soit organisé, qu'il fonctionne bien dans des scénarios particuliers et qu'il soit facile à utiliser. Dans cet ensemble de quatre ateliers de programmation, les améliorations que vous apportez à l'application GuessTheWord respectent les consignes de l'architecture de l'application Android, et vous utilisez les composants d'architecture Android. L'architecture de l'application Android est semblable au modèle architectural de MVVM (model-view-viewmodel).
L'application GuessTheWord suit le principe de séparation des préoccupations et est divisée en classes, chaque classe étant traitée selon une préoccupation distincte. Dans ce premier atelier de programmation, vous allez utiliser un contrôleur d'interface utilisateur, un ViewModel
et une ViewModelFactory
.
contrôleur d'interface utilisateur
Un contrôleur d'interface utilisateur est une classe basée sur une interface utilisateur telle que Activity
ou Fragment
. Un contrôleur d'interface utilisateur ne doit contenir que la logique qui gère les interactions de l'interface utilisateur et du système d'exploitation telles que l'affichage des vues et la saisie des entrées utilisateur. N'utilisez pas de logique de prise de décision, comme celle qui détermine le texte à afficher, dans le contrôleur d'UI.
Dans le code de démarrage de GuessTheWord, les contrôleurs d'interface utilisateur sont les trois fragments: GameFragment
, ScoreFragment,
et TitleFragment
. En suivant le principe de séparation des préoccupations, le GameFragment
est uniquement chargé de dessiner les éléments du jeu à l'écran et de savoir quand l'utilisateur appuie sur les boutons, et rien de plus. Lorsque l'utilisateur appuie sur un bouton, ces informations sont transmises au GameViewModel
.
ViewModel
Un ViewModel
contient des données à afficher dans un fragment ou une activité associée au ViewModel
. Un ViewModel
peut effectuer des calculs et des transformations simples sur les données pour préparer les données à afficher par le contrôleur de l'interface utilisateur. Dans cette architecture, le ViewModel
prend les décisions.
Le GameViewModel
contient des données telles que la valeur de score, la liste de mots et le mot actuel, car il s'agit des données à afficher à l'écran. Le GameViewModel
contient également la logique métier pour effectuer des calculs simples et déterminer l'état actuel des données.
ViewModelFactory
Un ViewModelFactory
instancie des objets ViewModel
, avec ou sans les paramètres de constructeur.
Dans les prochains ateliers de programmation, vous découvrirez d'autres composants d'architecture Android associés aux contrôleurs de l'interface utilisateur et à ViewModel
.
La classe ViewModel
est conçue pour stocker et gérer les données liées à l'interface utilisateur. Dans cette application, chaque ViewModel
est associé à un fragment.
Dans cette tâche, vous allez ajouter les premières ViewModel
de l'application (GameViewModel
) pour les GameFragment
. Vous en apprendrez également plus sur la signification de ViewModel
.
Étape 1: Ajouter 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 doit se compiler comme prévu. Si le problème persiste, essayez de résoudre le problème ou revenez à la version ci-dessous.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Dans le dossier
screens/game/
du package, créez une classe Kotlin appeléeGameViewModel
. - Faites en sorte que la classe
GameViewModel
étend la classe abstraiteViewModel
. - Pour mieux comprendre comment
ViewModel
tient compte du cycle de vie, ajoutez un blocinit
avec une instructionlog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Étape 2: Remplacez la règle onCleared() et ajoutez une journalisation
Le ViewModel
est détruit lorsque le fragment associé est dissocié ou lorsque l'activité est terminée. Juste avant la destruction de ViewModel
, le rappel onCleared()
est appelé pour nettoyer les ressources.
- Dans la classe
GameViewModel
, ignorez 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 associer les deux, vous devez créer une référence à ViewModel
dans le contrôleur d'interface utilisateur.
Au cours de cette étape, vous allez créer une référence à GameViewModel
dans le contrôleur d'interface utilisateur correspondant, à savoir GameFragment
.
- Dans la classe
GameFragment
, ajoutez un champ de typeGameViewModel
en tant que variable de niveau supérieur.
private lateinit var viewModel: GameViewModel
Étape 4: Initialisez le ViewModel
Lors de modifications de la configuration telles que les rotations d'écran, les contrôleurs d'interface tels que les fragments sont recréés. Cependant, les instances ViewModel
survivent. Si vous créez l'instance ViewModel
à l'aide de la classe ViewModel
, un objet est créé à chaque recréation du fragment. Créez plutôt l'instance ViewModel
à l'aide d'un ViewModelProvider
.
Fonctionnement de ViewModelProvider
:
ViewModelProvider
renvoie un objetViewModel
existant s'il en existe un, ou s'il en existe un autre.ViewModelProvider
crée une instanceViewModel
associée au champ d'application donné (activité ou fragment).- Le
ViewModel
créé est conservé tant que le champ d'application est actif. Par exemple, si la portée est un fragment, leViewModel
est conservé jusqu'à ce qu'il soit dissocié.
Initialisez ViewModel
, à l'aide de la méthode ViewModelProviders.of()
afin de créer une ViewModelProvider
:
- Dans la classe
GameFragment
, initialisez la variableviewModel
. Placez ce code dansonCreateView()
, après la définition de la variable de liaison. Utilisez la méthodeViewModelProviders.of()
, puis transmettez le contexteGameFragment
associé et la classeGameViewModel
. - Au-dessus de l'initialisation de l'objet
ViewModel
, ajoutez une instruction de journalisation pour consigner l'appel de 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 appliquez un filtre sur
Game
. Appuyez sur le bouton Lire sur votre appareil ou votre émulateur. L'écran du jeu s'ouvre.
Comme indiqué dans Logcat, la méthodeonCreateView()
deGameFragment
appelleViewModelProviders.of()
la méthodeGameViewModel
. Les instructions de journalisation que vous avez ajoutées àGameFragment
et àGameViewModel
s'affichent dans Logcat.
- Activez le paramètre de 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.ViewModelProviders.of()
est donc appelé à chaque fois. Cependant,GameViewModel
n'est créé qu'une seule fois, et n'est pas recréé ni détruit pour 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 quittez-le. Le
GameFragment
est 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 s'agit donc d'un bon emplacement pour les données devant survivre aux modifications de configuration:
- Placez les données à afficher et le code pour 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'interface utilisateur GameFragment
sont gérées dans l'application de départ avant d'ajouter ViewModel
et après ViewModel
:
- Avant d'ajouter
ViewModel
,
lorsque l'application subit une modification de configuration telle qu'une rotation de l'écran, le fragment de jeu est détruit, puis recréé. Les données sont perdues. - Après avoir ajouté
ViewModel
et déplacé les données d'interface utilisateur du fragment de jeu dansViewModel
,
toutes les données que le fragment doit afficher sont désormaisViewModel
. Lorsque l'application subit une modification de configuration, leViewModel
persiste et les données sont conservées.
Dans cette tâche, vous allez déplacer les données de l'interface utilisateur de l'application dans la classe GameViewModel
, ainsi que les méthodes de traitement des données. Ainsi, les données sont conservées en cas de modification de la configuration.
Étape 1: Déplacer les champs 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 variableGameFragmentBinding
, car elle contient des références aux vues. Elle permet de gonfler la mise en page, de configurer les écouteurs de clics et d'afficher les données à l'écran, c'est-à-dire 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 lors de la création deViewModel
, et non chaque fois que le fragment est créé. Vous pouvez supprimer l'instruction de journal dans le blocinit
deGameFragment
.
Les gestionnaires de clics onSkip()
et onCorrect()
dans GameFragment
contiennent du code permettant de traiter les données et de mettre à jour l'interface utilisateur. Le code permettant de mettre à jour l'interface utilisateur doit rester dans le fragment, mais celui du traitement des données doit être déplacé vers ViewModel
.
Pour l'instant, placez les méthodes identiques aux deux emplacements:
- Copiez les méthodes
onSkip()
etonCorrect()
deGameFragment
dansGameViewModel
. - Dans
GameViewModel
, assurez-vous que les méthodesonSkip()
etonCorrect()
ne sont pasprivate
, car vous les référencerez à partir du fragment.
Voici le code pour la classe GameViewModel
, après refactorisation:
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 pour la classe GameFragment
, après refactorisation:
/**
* 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 pour mettre à jour le score, et 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 denextWord()
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 elles se trouvent désormais dansGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- Dans le
GameViewModel
, dans la méthodenextWord()
, supprimez les appels aux méthodesupdateWordText()
etupdateScoreText()
. Ces méthodes sont maintenant appelées depuisGameFragment
. - Créez l'application et assurez-vous qu'elle ne contient pas d'erreurs. Si vous rencontrez des erreurs, nettoyez et recréez le projet.
- Exécutez l'application et jouez en utilisant quelques mots. Sur l'écran du jeu, faites pivoter l'appareil. Notez que le score actuel et le mot actuel sont conservés après le changement d'orientation.
Bravo ! Toutes les données de votre application sont désormais 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 le jeu).
- Dans
GameFragment
, ajoutez une méthode appeléeonEndGame()
. La méthodeonEndGame()
est appelée lorsque l'utilisateur appuie sur le bouton End Game (Terminer le jeu).
private fun onEndGame() {
}
- Dans
GameFragment
, à l'intérieur de la méthodeonCreateView()
, localisez le code qui définit les écouteurs de clics pour les boutons OK et Passer. Juste en dessous de ces deux lignes, définissez un écouteur de clics pour le bouton End Game (Terminer le jeu). 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 de score. 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 au jeu et faites défiler les mots. Appuyez sur le bouton Terminer le jeu. Notez que l'application accède à l'écran de score, mais que le score final ne s'affiche pas. Vous allez résoudre ce problème à la tâche suivante.
Lorsque l'utilisateur termine le jeu, le ScoreFragment
n'affiche pas le score. Vous souhaitez qu'une ViewModel
tienne le score affiché par le ScoreFragment
. Vous transmettrez la valeur de score lors de l'initialisation de ViewModel
à l'aide du modèle de méthode d'usine.
Le modèle de méthode de fabrique est un modèle de conception créative qui utilise des méthodes de fabrique pour créer des objets. Une méthode d'usine 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 ViewModel
.
- Sous le package
score
, créez une classe Kotlin appeléeScoreViewModel
. Cette classe sera leViewModel
du fragment de score. - Prolongez la classe
ScoreViewModel
deViewModel.
Ajoutez un paramètre 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
. - Prolongez la classe
ScoreViewModelFactory
deViewModelProvider.Factory
. Ajoutez un paramètre 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, ignorez la méthodecreate()
. Dans la méthodecreate()
, renvoyez l'objetScoreViewModel
qui vient d'être 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
. Utilisez la propriétéScoreViewModelFactory
. Transmettez le score final du groupe d'arguments en tant que paramètre constructeur àScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- Dans
onCreateView(
, initialisezviewModelFactory
, puis initialisez l'objetviewModel
. Appelez la méthodeViewModelProviders.of()
, transmettez le contexte du fragment de score associé etviewModelFactory
. Cette commande crée l'objetScoreViewModel
à l'aide de la méthode d'usine 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 dans leScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Exécutez votre application et jouez au jeu. Faites défiler une partie ou la totalité des mots, puis appuyez sur Arrêter le jeu. Notez que le fragment de score affiche maintenant le score final.
- Facultatif: Vérifiez les journaux
ScoreViewModel
dans le fichier logcat en filtrant surScoreViewModel
. La valeur du score doit être affichée.
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 ViewModel
en utilisant 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. Désormais, les données du jeu survivent aux modifications de configuration. Vous avez également appris à créer un constructeur paramétré pour créer une ViewModel
à l'aide de l'interface ViewModelFactory
.
Projet Android Studio: GuessTheWord
- Les consignes de l'architecture des applications Android recommandent de séparer les classes qui ont des responsabilités différentes.
- Un contrôleur d'interface utilisateur est une classe basée sur une interface utilisateur telle que
Activity
ouFragment
. Les contrôleurs UI ne doivent contenir que la logique qui gère les interactions avec l'interface utilisateur et le système d'exploitation. Ils ne doivent pas contenir de données à afficher dans l'interface utilisateur. Placez ces données dans unViewModel
. - La classe
ViewModel
stocke et gère les données liées à l'interface utilisateur. La classeViewModel
permet aux données de survivre aux modifications de 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'interface utilisateur aux instances ViewModel
qui contiennent des données:
Contrôleur d'interface utilisateur | ViewModel |
|
|
ne contient pas de données à afficher dans l'interface utilisateur. | Contient les données que le contrôleur de l'UI affiche dans l'UI. |
Contient le code permettant d'afficher les données, ainsi que le code des événements utilisateur, tels que les écouteurs de clics. | Contient du code pour le traitement des données. |
détruite et recréée à chaque modification de la configuration ; | Elle n'est détruite que lorsque le contrôleur de l'interface utilisateur disparaît définitivement, pour une activité, lorsqu'elle se termine ou pour un fragment détaché. |
Contient des vues. | Ne doit jamais contenir de références à des activités, des fragments ou des vues, car ces éléments ne peuvent pas survivre aux modifications de configuration, contrairement à la fonction |
Contient une référence au | ne contient aucune référence au contrôleur de l'interface utilisateur associé. |
Cours Udacity:
Documentation pour les développeurs Android:
- Présentation de ViewModel
- Gérer les cycles de vie avec les composants du cycle de vie
- Guide de l'architecture des applications
ViewModelProvider
ViewModelProvider.Factory
Autre :
- Modèle d'architecture MVVM (model-view-viewmodel).
- Principes de conception de Séparation des préoccupations
- Modèle de méthode d'usine
Cette section répertorie les devoirs possibles pour les élèves qui effectuent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. C'est à l'enseignant de procéder comme suit:
- Si nécessaire, rendez-les.
- Communiquez aux élèves comment rendre leurs devoirs.
- Notez les devoirs.
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ils n'ont pas besoin d'attribuer les devoirs de leur choix.
Si vous suivez vous-même cet atelier de programmation, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondez à ces questions.
Question 1
Pour éviter de perdre des données lors d'une modification de la configuration de l'appareil, dans quelle classe devez-vous enregistrer les données de l'application ?
ViewModel
LiveData
Fragment
Activity
Question 2
Une propriété ViewModel
ne doit jamais contenir de références à des fragments, des activités ou des vues. Vrai ou faux ?
- Vrai
- Faux
Question 3
Quand un ViewModel
est-il détruit ?
- Lorsque le contrôleur d'interface utilisateur associé est détruit et recréé en cas de 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 qu'il est dissocié (s'il s'agit d'un fragment).
- Lorsque l'utilisateur appuie sur le bouton "Retour".
Question 4
À quoi sert l'interface ViewModelFactory
?
- Instancier un objet
ViewModel
. - Conserver les données lors de changements d'orientation.
- Actualisation des données affichées à l'écran
- Recevoir des notifications lorsque les données de l'application sont modifiées
Démarrez la leçon suivante:
Pour obtenir des liens vers d'autres ateliers de programmation dans ce cours, consultez la page de destination des ateliers de programmation Android Kotlin Fundamentals.