• 10 hours
  • Easy

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 3/12/24

Implémentez la couche ViewModel

 

Liez le ViewModel avec le repository

Dans ce chapitre, nous allons établir le lien crucial entre le ViewModel et le repository. C'est une étape essentielle pour permettre à notre interface utilisateur d'accéder aux données de manière réactive et asynchrone.

Le ViewModel agit comme un chef d'orchestre pour notre interface utilisateur, coordonnant l'accès aux données et la mise à jour de l'état de la vue (state). Nous allons maintenant créer une méthode dans le ViewModel qui utilisera la nouvelle méthode asynchrone du repository.

Créons une classe nommée  presentation.home.HomeViewModel  , comme nous en avons déjà créé auparavant.

Création d'une nouvelle classe
Création d'une nouvelle classe
import android.util.Log
import androidx.lifecycle.ViewModel
import com.openclassrooms.stellarforecast.data.repository.WeatherRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
import androidx.lifecycle.viewModelScope
import com.openclassrooms.stellarforecast.domain.model.WeatherReportModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
 
 
@HiltViewModel
class HomeViewModel @Inject constructor(private val dataRepository: WeatherRepository) :
ViewModel() {
 
init {
getForecastData()
}
 
private fun getForecastData() {
val latitude = 48.844304
val longitude = 2.374377
dataRepository.fetchForecastData(latitude, longitude)
           .onEach { forecastUpdate ->
               Log.i("HomeViewModel", forecastUpdate.toString())
           }
           .launchIn(viewModelScope)
}
}

Ouh la la ça a l’air complexe à première vue ! Qu’est-ce que je dois comprendre là-dedans ? 

Pas de panique ! Voici une explication détaillée du code fourni :

  • @HiltViewModel : Cette annotation indique à Hilt que la classe HomeViewModel doit être injectée automatiquement.

  • Inject constructor  : Le constructeur de la classe est annoté avec  @Inject  , ce qui permet à Hilt de fournir automatiquement l'instance de WeatherRepository lorsqu'un objet  HomeViewModel  est créé. Comme toujours, Hilt s’occupe de la création de nos classes pour nous sans que nous ayons rien à faire de plus.

  • init : La fonction init est exécutée lors de l’initialisation d'une instance de  HomeViewModel  . Dans ce cas, elle appelle la fonction  getForecastData()  .

  • getForecastData()  : Cette fonction sert à récupérer les prévisions météorologiques. Elle utilise une latitude et une longitude prédéfinies pour Paris.

  • dataRepository.fetchForecastData : Cela appelle la fonction  fetchForecastData  du WeatherRepository, récupérant ainsi les données de manière asynchrone à l'aide de coroutines.

    • onEach  : Cette fonction est appelée chaque fois que de nouvelles données sont émises par la coroutine. 

    • launchIn(viewModelScope) : Cette partie indique que la coroutine doit être lancée dans le scope du ViewModel. Cela garantit que la coroutine est annulée lorsque le ViewModel est détruit, évitant les fuites de mémoire. 

En résumé, ce ViewModel utilise Hilt pour l'injection de dépendances et WeatherRepository pour obtenir les prévisions météorologiques. La fonction  getForecastData()  , initialisée lors de la création du ViewModel, imprime chaque nouvelle donnée disponible.

Cette approche réactive garantit que l'interface utilisateur est mise à jour en temps réel à mesure que de nouvelles données deviennent disponibles.

Cependant, nous devons également adapter notre interface utilisateur pour pouvoir observer ces changements. C'est là que l'utilisation des StateFlows devient pertinente.

Mettez à jour le state / l'état de la vue

Nous allons maintenant mettre à disposition des données que notre vue va pouvoir consommer directement.

Ajoutez cette ligne de code après l'accolade ouvrante de  HomeViewModel  :

// Expose screen UI state
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

Puis, après l’accolade fermante de  HomeViewModel  :

data class HomeUiState(
   val forecast: List<WeatherReportModel> = emptyList(),
)

Expliquons le code précédent :

  • MutableStateFlow : Il s'agit d'une implémentation de  StateFlow  qui permet de représenter un flux de données modifiable. Dans ce cas, on crée une instance de  MutableStateFlow  initialisée avec une nouvelle instance de  HomeUiState  , qui est une classe modélisant l'état de l'interface utilisateur de l'écran d'accueil.

  • StateFlow  : C'est une classe du framework Kotlin Flow qui émet une séquence de valeurs et garantit qu'un observateur reçoit toujours la dernière valeur émise. Dans cet exemple, on expose un  StateFlow  en lecture seule à partir du  MutableStateFlow  créé précédemment.

L'objectif de ce mécanisme est de représenter et de gérer l'état actuel de l'interface à un seul et même endroit. Ainsi, le rendu graphique de notre vue ne sera qu’une représentation explicite de notre état. Il faut voir l'état comme un plan de construction où le rendu graphique est la construction.

Pourquoi est-ce nécessaire d'utiliser ce mécanisme ? Alors que l'on pourrait envisager l'utilisation d'une simple variable publique accessible. 

  • Réactivité : En utilisant StateFlow, toute partie de l'application qui observe uiState est automatiquement notifiée et mise à jour lorsqu'une nouvelle valeur est émise. Cela offre un mécanisme réactif pour refléter les changements d'état dans l'interface utilisateur en temps réel.

  • Maintenir l'état : StateFlow maintient l'état actuel de l'interface utilisateur, ce qui est crucial dans une application Android où les changements d'orientation ou d'autres événements peuvent se produire, entraînant la destruction et la recréation d'activités ou de fragments.

  • Cohérence des données : En utilisant un flux de données d'état, on s'assure que toutes les parties de l'application accèdent à une source unique et cohérente de l'état de l'interface utilisateur.

En résumé, l'utilisation de StateFlow permet de gérer efficacement et de manière réactive l'état de l'interface utilisateur dans une application Android, assurant la cohérence des données et la réactivité aux changements d'état.

Maintenant, modifions la méthode  getForecastData  pour qu’elle mette à jour  _uiState  .

dataRepository.fetchForecastData(latitude, longitude).onEach { forecastUpdate ->
_uiState.update { currentState ->
currentState.copy(
forecast = forecastUpdate,
)
}
}.launchIn(viewModelScope)

Analysons le code morceau par morceau :

  • _uiState.update { currentState -> :  _uiState  est une instance de  HomeUiState  contenue dans un  MutableStateFlow  qui représente l'état de l'interface utilisateur. update est une fonction de  MutableStateFlow  qui permet de mettre à jour son état tout en garantissant la cohérence de l'accès concurrent.

  • currentState.copy(forecast = forecastUpdate) : Cette ligne crée une nouvelle instance de l'état de l'interface utilisateur (HomeUiState) en copiant l'état actuel (currentState) et en remplaçant la propriété forecast par la nouvelle valeur de forecastUpdate. 

Cela suit le modèle immuable de Kotlin, où la modification d'un objet crée une nouvelle instance avec les modifications souhaitées.

À vous de jouer

Contexte

La montagne est dorénavant complètement blanche, mais aucune urgence, les températures sont encore fraîches. Les canons ne seront pas utilisés avant plusieurs jours.

Ce qui vous laisse le temps de commencer à définir votre vue utilisateur, en commençant par le couple ViewModel-State.

En effet, ils sont les serviteurs de toute vue qui affiche des données cohérentes. Notre State contiendra une liste de prévisions météorologiques.

Consignes

Votre mission actuelle consiste à :

  • définir une classe  ViewModel  ;

  • définir une classe  State  .

Corrigé

Le corrigé est disponible sur GitHub à cette adresse

  • Déclaration du  ViewModel  .

  • Déclaration du  State  .

En résumé

  • Hilt facilite la gestion des dépendances en injectant automatiquement les instances nécessaires, rendant le code plus propre et plus lisible.

  • Le ViewModel agit comme un chef d’orchestre, utilisant Hilt pour l'injection de dépendances et le repository pour obtenir les données météorologiques.

  • La fonction  getForecastData()  du ViewModel utilise une coroutine pour récupérer de manière asynchrone les prévisions météorologiques du repository.

  • L'utilisation de  StateFlow  garantit que l'interface utilisateur est mise à jour en temps réel à mesure que de nouvelles données deviennent disponibles.

  • L'utilisation de  StateFlow  pour représenter l'état de l'interface utilisateur assure la cohérence des données, la réactivité aux changements d'état et la gestion efficace des événements système tels que les changements d'orientation.

Nous possédons donc un objet HomeUiState prêt à être affiché ! Nous possédons toutes les données nécessaires pour afficher une belle liste de prévisions météorologiques avec un RecyclerView dans le prochain chapitre.

Example of certificate of achievement
Example of certificate of achievement