• 10 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 12/03/2024

Affichez les données récupérées

Gérez l’UI selon l’état réseau

Bonjour chère étoile filante, 

La phase tant attendue d'affichage des données est enfin arrivée avec le design d'Adele que tu retrouveras dans ce mail !

Exemple de design
Exemple de design

L'utilisation du RecyclerView peut sembler un peu délicate, mais rien d’insurmontable. Prends ton temps pour comprendre toutes les parties qui le composent. Nous allons mettre en place les StateFlows à présent, une manière élégante de mettre à jour l’interface. 

Prêt à illuminer l'écran ?

Des journées claires, même avec des bugs.

Cordialement,

Margaret Hamilton, Dev Senior – Projet "Planète Exploration"

Explorez le fonctionnement des StateFlows

Les  StateFlows  sont particulièrement adaptés pour gérer l'état dynamique de l'interface utilisateur. Dans notre cas, nous utiliserons un  StateFlow  (HomeUiState) pour représenter l'état actuel des données météo dans l'interface utilisateur.

Nous allons modifier la classe  MainActivity  , automatiquement générée par Android Studio à la création du projet, par ce code :

import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.openclassrooms.stellarforecast.databinding.ActivityMainBinding
import com.openclassrooms.stellarforecast.domain.model.WeatherReportModel
import com.openclassrooms.stellarforecast.presentation.home.HomeViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
 
 
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
 
private lateinit var binding: ActivityMainBinding
   private val viewModel: HomeViewModel by viewModels()
   //TODO 1
 
   override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
       binding = ActivityMainBinding.inflate(layoutInflater)
       setContentView(binding.root)
defineRecyclerView()
 
       lifecycleScope.launch {
           repeatOnLifecycle(Lifecycle.State.STARTED) {
               viewModel.uiState.collect {
                   updateCurrentWeather(it.forecast)
               }
           }
       }
   }
 
private fun updateCurrentWeather(forecast: List<WeatherReportModel> {
       //TODO 2
   }
 
private fun defineRecyclerView(){
       //TODO 3
   }
}

Ce code concerne la configuration et l'interaction de l'activité principale avec le ViewModel. Voici une explication détaillée :

  • private val viewModel: HomeViewModel by viewModels()  : Déclare une propriété viewModel qui utilise la fonction  viewModels()  pour obtenir une instance du ViewModel associé à cette activité. L'utilisation de  by viewModels()  est une façon recommandée de créer et d'accéder au ViewModel dans une activité.

  • lifecycleScope.launch { ... }  : Utilise le lifecycleScope de l'activité pour lancer une coroutine.

  • repeatOnLifecycle(Lifecycle.State.STARTED) { ... } : Cette fonction du lifecycleScope assure que le bloc de code à l'intérieur est exécuté tant que le cycle de vie de l'activité est dans l'état STARTED. Cela garantit que les opérations de collecte (écoute) sur le  StateFlow  sont actives lorsque l'activité est visible à l'utilisateur.

  • viewModel.uiState.collect { ... } : Utilise la fonction collect sur le StateFlow uiState du ViewModel pour écouter les mises à jour de l'état de l'interface utilisateur de manière réactive. En théorie, un flow peut émettre plusieurs valeurs à la suite.

  • uiState.forecast?.let { forecast -> ... } : Vérifie si l'objet forecast dans l'état de l'interface utilisateur n'est pas nul, puis exécute le bloc de code à l'intérieur. Cela permet d'effectuer des actions spécifiques lorsque de nouvelles données météorologiques sont disponibles.

Nous disposons maintenant de nos données de météo dans la plus haute couche de notre application, c'est-à- dire l’interface utilisateur. Il ne nous reste plus qu'à la dessiner ! Nous reviendrons sur les  //TODO  dans le chapitre suivant.

Implémentez la RecyclerView

La RecyclerView est un composant Android et flexible conçu pour afficher une liste de données de manière efficace. Son utilisation est recommandée parce que :

  • Efficace : Le RecyclerView recycle les éléments de vue, minimalisant ainsi la consommation de ressources.

  • Flexible : Il offre la possibilité de personnaliser la disposition, le comportement et l'apparence des éléments de la liste.

  • Gère les modifications de données : Le RecyclerView gère de manière efficace les modifications dynamiques de la liste grâce à des fonctionnalités telles que  DiffUtil  . 

Fonctionnement du Recycler View
Fonctionnement du Recycler View

Un RecyclerView est composé de plusieurs éléments essentiels pour permettre l'affichage efficace d'une liste de données dans une interface utilisateur. Ces composants sont les suivants :

  • Adapter :

L'Adapter a pour rôle de lier les données à la vue. Il agit comme un pont entre les données, généralement stockées dans une liste, et la vue qui les affiche. L'adapter crée également les ViewHolders nécessaires pour chaque élément de données, ce qui permet un recyclage efficace des vues et une meilleure performance.

  • ViewHolder :

Un ViewHolder représente chaque élément individuel dans la liste. Il conserve une référence aux vues à l'intérieur de chaque élément de la liste, ce qui évite de rechercher ces vues à chaque mise à jour.

  • LayoutManager : 

Le LayoutManager est responsable de la disposition des éléments dans le RecyclerView. Il détermine comment les éléments sont arrangés, s'ils forment une liste verticale, horizontale ou une grille.

Affichez les éléments de la liste avec ViewHolder

Nous allons implémenter le code pour nous permettre d’afficher cette liste fournie par Margaret :

Affichage de la liste
Affichage de la liste

Ouvrez le fichier déjà présent “main_activity.xml", et remplacez le code par une déclaration de RecyclerView :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
 
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
tools:listitem="@layout/item_weather"
android:layout_height="match_parent" />
 
</RelativeLayout>

Ajoutez un layout “item_weather.xml” dans le sous-dossier “layout” du dossier “res”, comme vous l’avez vu lors des précédents cours. Voici le code qu’il faut ajouter :

<!-- item_weather.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
androidrientation="horizontal"
androidadding="16dp">
 
<TextView
android:id="@+id/textViewDateTime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18sp"
android:textStyle="bold"
tools:text="15/01 - 16:00" />
 
<TextView
android:id="@+id/textViewStargazing"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
tools:text="👍" />
</LinearLayout>

Nous affichons sur une ligne les informations météorologiques. C’est l'élément que le ViewHolder viendra compléter et modifier au besoin. L'option tools:text  ou tools:listitem  est utilisée pour afficher des données fictives afin d'obtenir une représentation visuelle uniquement dans Android Studio.

Liez les données au design avec Adapter

Nous allons créer une nouvelle classe nommée  presentation.home.WeatherAdapter  , comme nous l'avons déjà créée précédemment.

Création d'une nouvelle classe
Création d'une nouvelle classe

L'Adapter sert à lier les données à la RecyclerView et à créer les vues pour chaque élément de la liste. Voici comment vous pouvez définir un Adapter pour votre RecyclerView :

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.openclassrooms.stellarforecast.databinding.ItemWeatherBinding
import com.openclassrooms.stellarforecast.domain.model.WeatherReportModel
import java.text.SimpleDateFormat
import java.util.Locale
 
 
class WeatherAdapter() : ListAdapter<WeatherReportModel, WeatherAdapter.WeatherViewHolder>(DiffCallback) {
 
class WeatherViewHolder(
private val binding: ItemWeatherBinding,
   ) : RecyclerView.ViewHolder(binding.root) {
 
private val dateFormatter = SimpleDateFormat("dd/MM - HH:mm", Locale.getDefault())
 
fun bind(observation: WeatherReportModel) {
           val formattedDate: String = dateFormatter.format(observation.date.time)
           binding.textViewDateTime.text = formattedDate
           binding.textViewStargazing.text = if (observation.isGoodForStargazing) "⭐️" else "☁️"
}
}
 
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeatherViewHolder {
       val itemView = ItemWeatherBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WeatherViewHolder(itemView)
}
 
override fun onBindViewHolder(holder: WeatherViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
}
 
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<WeatherReportModel>() {
override fun areItemsTheSame(oldItem: WeatherReportModel, newItem: WeatherReportModel): Boolean {
return oldItem.date == newItem.date
}
 
override fun areContentsTheSame(oldItem: WeatherReportModel, newItem: WeatherReportModel): Boolean {
return oldItem == newItem
}
}
}
}

Voici une explication des fonctions clés de l’Adapter :

  • WeatherAdapter  :

    WeatherViewHolder  : Classe interne représentant chaque élément de la liste, responsable de la liaison des données avec les vues.

    onCreateViewHolder  : Crée et retourne un nouvel objet ViewHolder pour chaque élément de la liste.

    onBindViewHolder  : Associe l'élément à la vue correspondante dans le ViewHolder. 

  • WeatherViewHolder :

    bind  : Lie les propriétés des données de prévisions météorologiques aux éléments de la vue, tels que la température, les températures maximale et minimale, ainsi que les précipitations. C’est la méthode que vous modifierez le plus afin d’afficher les données souhaitées.

  • DiffCallback :

    areItemsTheSame et areContentsTheSame  : Aide à déterminer quelles données ont changé entre les anciennes et les nouvelles listes d'éléments. Cela aide à optimiser l'affichage en mettant à jour uniquement les parties nécessaires de l'interface utilisateur lorsqu'il y a des changements. Dans notre cas, nous vérifions seulement si la date change.

Liez le State avec les vues ItemView

Pour maintenir une interface utilisateur réactive et à jour, il est crucial de lier le State (état) de votre application avec les vues de la RecyclerView. Revenons dans MainActivity et remplaçons les  TODO  .

Remplacer le  TODO 1  par :

private val customAdapter = WeatherAdapter()

Remplacer le  TODO 2  par :

private fun updateCurrentWeather(forecast: List<WeatherReportModel> {
customAdapter.submitList(forecast)
}

En utilisant la fonction collect, nous observons les changements d'état. À chaque émission d'une nouvelle liste de données, l'ensemble des données de l'Adapter de la RecyclerView est mis à jour, assurant ainsi une représentation en temps réel des informations dans l'interface utilisateur.

Ainsi, cette approche garantit une gestion efficace des changements d'état, une mise à jour réactive de l'interface utilisateur et une expérience utilisateur fluide au sein de votre application Android.

Contrôlez la disposition des éléments avec LayoutManager

Le LayoutManager contrôle la disposition des éléments de la RecyclerView. Vous pouvez choisir parmi plusieurs gestionnaires, tels que  LinearLayoutManager  ,  GridLayoutManager  ou  StaggeredGridLayoutManager  , selon vos besoins. Voici comment définir un  LinearLayoutManager  :

Mettez à jour les  TODO 3  :

private fun defineRecyclerView(){
val layoutManager = LinearLayoutManager(applicationContext)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = customAdapter
}

À vous de jouer

Contexte

En arrivant ce matin, vous vous rendez compte que tout le monde a posé son manteau, serait-ce le début d’un redoux ?

Parfait ! Votre application est presque terminée, c’est le moment d'intégrer les derniers éléments graphiques au sein d’une RecyclerView.

  • Nous afficherons une liste de données météorologiques, ordonnée par heure, indiquant l’heure et si nous avons l’accord pour faire tourner les canons à neige. 

  • L’accord est régi par une température inférieure à zéro et des précipitations nulles.

On frappe à votre porte, un collègue vous demande :

Est-ce que l’app sera prête pour ce soir ? Car c’est le Grand Soir !

Consignes

Votre mission actuelle consiste à :

  • Définir une classe Recyclerview et ses dépendances : Adapter, la vue XML des Item, l’ajout du RecyclerView dans l’Activity.

Corrigé

Le corrigé est disponible sur GitHub à cette adresse

  • Déclaration du RecyclerView.

  • Déclaration de l’Adapter.

  • Mise à jour du XML de l’Activity.

  • La définition de la vue des Items XML.

En résumé

  • Un RecyclerView est un composant d'Android utilisé pour afficher une liste de données dynamique dans une interface utilisateur ; il utilise un Adapter pour lier les données à la vue qui correspondent aux ViewHolders, représentant chaque élément de la liste.

  • L'Adapter gère la création des ViewHolders (  onCreateViewHolder  ), l'association des données avec les vues (  bind  ) et la gestion des mises à jour dynamiques de l'interface utilisateur.

  • Les ViewHolders représentent la partie graphique de la RecyclerView, ce sont les éléments de la liste que nous lions à un fichier XML. 

Maintenant que nous avons compris le fonctionnement de base du RecyclerView, plongeons dans les interactions utilisateur, en particulier la gestion des clics sur les éléments de la liste.

Exemple de certificat de réussite
Exemple de certificat de réussite