• 8 hours
  • Easy

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 9/4/23

Implémentez le ViewModel de l’écran de quiz

Découvrez le pattern ViewModel

Pour rappel, le pattern ViewModel a deux objectifs : 

  1. Exposer des données, ou états, représentant les informations dont l’interface a besoin.

  2. Fournir des fonctions correspondant aux événements qui ont lieu sur l’interface et qui nécessitent la modification des données.

La classe ViewModel fournie par Google

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 {}    .

Créez une classe de type 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  .

Organisez le package ui

Vous 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.

Créez la classe QuizViewModel

Maintenant 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  .

Capture d’écran de l’arborescence de fichiers d'Android Studio, montrant l’organisation des fichiers en différents packages. On y voit en particulier au sein du package principal fr.mycompany.superquiz les deux packages suivants : data et ui.
Organisation des fichiers de notre projet dans différents packages

À vous de jouer !

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.

Identifiez les états de l’écran de quiz

À 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.

1. Les observateurs s'abonnent à l'observable. 2. La valeur de l'observable change. 3. Les observateurs sont notifiés de la nouvelle valeur.
Pattern Observer

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  .

Exposez les états grâce aux LiveData

Un 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.

Identifiez les événements de l’écran de quiz

À 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  .

À vous de jouer !

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;
    }
}

Récapitulons en vidéo

Retrouvez ces différentes étapes dans la vidéo ci-dessous :

En résumé

  • 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. 

Example of certificate of achievement
Example of certificate of achievement