Pour rappel, le pattern ViewModel a deux objectifs :
Exposer des données, ou états, représentant les informations dont l’interface a besoin.
Fournir des fonctions correspondant aux événements qui ont lieu sur l’interface et qui nécessitent la modification des données.
Pour enrichir davantage ce pattern qui n’est pas spécifique à Android, Google fournit une classe, nommée également ViewModel , qui a la capacité de stocker les données qu’elle contient en tenant compte du cycle de vie de la vue associée. Plus spécifiquement, grâce à cette classe ViewModel, les données stockées dans des variables vont pouvoir survivre aux modifications de configuration de notre application, telles que les rotations d'écran.
Pour utiliser cette classe, il suffit de l’étendre en déclarant par exemple public class QuizViewModel extends ViewModel {} .
Commençons par organiser correctement notre projet. Il est d’usage de positionner tout le code relatif à la gestion de l’interface d’une fonctionnalité au même endroit, habituellement au sein d’un package nommé ui .
uiVous pouvez donc créer un package ui de la même manière que vous l’avez fait dans le chapitre précédent. Ensuite vous pouvez déplacer dans le package ui tout le code de notre application relatif à la gestion de l’interface utilisateur, c’est-à-dire :
QuizFragment ;
WelcomeFragment .
Pour cela, il suffit de faire un drag & drop de ces fichiers dans le package cible.
QuizViewModelMaintenant que tout est bien rangé, il est temps de créer cette fameuse classe de type ViewModel que nous nommerons QuizViewModel , en référence à la fonctionnalité de quiz qu’elle permet de gérer.
Comme une classe de type ViewModel gère la logique relative à une interface, nous pouvons ranger ce type de classe dans le package ui . Dans notre cas, nous pouvons donc créer QuizViewModel dans le package ui.quiz .

Vous allez pouvoir implémenter la classe QuizViewModel . Elle étend la classe ViewModel fournie par Android, et prend en paramètre la classe QuestionRepository , le fameux médiateur qui nous fournit les données dont nous avons besoin : la liste de questions.
Voici ce que vous devriez obtenir :
public class QuizViewModel extends ViewModel {
private QuestionRepository questionRepository;
public QuizViewModel(QuestionRepository questionRepository) {
this.questionRepository = questionRepository;
}
}Il est maintenant temps de compléter QuizViewModel avec le code relatif à la logique du quiz. Pour cela, nous allons concrètement devoir identifier les états et les événements dont notre interface va avoir besoin.
À l’échelle de l’écran de quiz de notre application, les états permettant de définir l’affichage sont :
la question en cours ;
savoir si l’utilisateur est arrivé à la dernière question, pour afficher le bouton “Finish” ;
le score en particulier pour afficher le score final.
Ces états doivent donc être partagés avec la vue ; dans notre cas, avec le fragment QuizFragment . Pour les partager, nous allons utiliser le design pattern Observer.
Encore un pattern ? Mais à quoi sert-il ?
Le pattern Observer repose sur le principe suivant : un observable émet des informations. Un ou des observateurs reçoivent ces informations.
Autrement dit, lorsqu’une variable de type observable est modifiée, elle va notifier ses observateurs de ce changement. Les observateurs vont pouvoir réagir à ces changements en les reflétant dans l’interface.

Pour pouvoir créer une telle variable capable de notifier des observateurs, tout en tenant compte du cycle de vie particulier d’une application Android, Google fournit les LiveData .
LiveDataUn LiveData peut contenir n’importe quel type de donnée : un type primitif comme un Integer , ou un objet spécifique à notre application, comme Question .
Pour créer un LiveData , il faut utiliser la classe MutableLiveData qui prend en paramètre une valeur initiale. Par exemple, pour initialiser l’état contenant la question en cours, ajoutez le code suivant dans la classe QuizViewModel :
MutableLiveData<Question> currentQuestion = new MutableLiveData<Question>();Ainsi déclarée, la valeur initiale contenue dans ce LiveData est nulle, car nous n’avons rien renseigné en paramètre de l’objet MutableLiveData .
Plus tard, pour mettre à jour la valeur contenue dans ce LiveData , il suffira d’appeler la fonction postValue() comme ceci :
currentQuestion.postValue(questions.get(1));Pour lire le contenu d’un LiveData , il suffit d’appeler la fonction getValue() ; par exemple : currentQuestion.getValue(); .
Pour le moment, restons dans la classe QuizViewModel . Nous pouvons créer les deux autres états dont a besoin l’interface, soit :
un booléen permettant de savoir si l’utilisateur est arrivé à la dernière question, initialisé à faux ;
le score initialisé à 0.
Cela donne :
MutableLiveData<Integer> score = new MutableLiveData<Integer>(0);
MutableLiveData<Boolean> isLastQuestion = new MutableLiveData<Boolean>(false);Les états de QuizViewModel sont maintenant initialisés, et ils n’attendent plus que d’être modifiés suite aux événements ayant lieu sur l’interface.
À l’échelle de l’écran de quiz de notre application, les événements qui vont impacter l’état de l’interface sont :
lorsque l’utilisateur arrive pour la première fois sur le quiz, celui-ci démarre ;
lorsque l’utilisateur clique sur une réponse à une question, pour connaître la validité de la réponse ;
lorsque l'utilisateur clique sur “Next” pour passer à la question suivante.
Cela se traduit par la création de trois fonctions dans notre ViewModel :
startQuiz ;
isAnswerValid ;
nextQuestion .
Essayez de coder seul(e) ces trois fonctions. Si besoin, voici quelques pistes pour leur conception :
la fonction startQuiz récupère les questions via QuestionRepository , et les stockent dans une variable globale. Elle initialise également l’état currentQuestion avec la première question ;
la fonction isAnswerValid prend en paramètre l’index de la réponse, et retourne un booléen selon que la réponse est vraie ou fausse. Pour savoir si la réponse est correcte, elle utilise l’état currentQuestion afin de récupérer l’index de bonne réponse. Si la réponse est correcte, elle met également à jour l’état correspondant au score ;
la fonction nextQuestion met à jour l’état currentQuestion avec la question suivante, ainsi que l’état isLastQuestion dans le cas où l’utilisateur arrive à la dernière question. Cette fonction nécessite la création d’une variable globale contenant l’index de la question en cours.
Voici la correction :
public class QuizViewModel extends ViewModel {
private QuestionRepository questionRepository;
private List<Question> questions;
private Integer currentQuestionIndex = 0;
public QuizViewModel(QuestionRepository questionRepository) {
this.questionRepository = questionRepository;
}
MutableLiveData<Question> currentQuestion = new MutableLiveData<Question>();
MutableLiveData<Integer> score = new MutableLiveData<Integer>(0);
MutableLiveData<Boolean> isLastQuestion = new MutableLiveData<Boolean>(false);
public void startQuiz(){
questions = questionRepository.getQuestions();
currentQuestion.postValue(questions.get(0));
}
public void nextQuestion() {
Integer nextIndex = currentQuestionIndex + 1;
if(nextIndex >= questions.size()) {
return; // should not happened as the 'Next' button is not displayed at the last question
} else if (nextIndex == questions.size() - 1) {
isLastQuestion.postValue(true);
}
currentQuestionIndex = nextIndex;
currentQuestion.postValue(questions.get(currentQuestionIndex));
}
public Boolean isAnswerValid(Integer answerIndex) {
Question question = currentQuestion.getValue();
boolean isValid = question != null && question.getAnswerIndex() == answerIndex;
Integer currentScore = score.getValue();
if(currentScore != null && isValid) {
score.setValue(currentScore + 1);
}
return isValid;
}
}Retrouvez ces différentes étapes dans la vidéo ci-dessous :
Il est d’usage de positionner tous les fichiers relatifs à l’interface d’une application au même endroit, par exemple un package nommé ui .
Le ViewModel contient la logique spécifique à l’affichage d’un écran sous forme d’états et d’événements.
Google fournit une classe ViewModel spécifique à Android, qui permet de stocker les variables qu’elle contient en tenant compte des changements de configuration de la vue qui la manipule.
Google fournit les LiveData pour exposer les états à la vue sous forme d’observables.
On initialise un LiveData via la classe MutableLiveData . Pour lire sa valeur, on utilise la fonction getValue() ; pour remplacer sa valeur, on utilise la fonction postValue() .
Bravo ! Vous avez parcouru un sacré chemin dans la structuration du code de votre application. C’est l’heure de la dernière étape, la dynamisation des composants de notre interface grâce aux états et événements que vous venez de créer dans QuizViewModel.