Cet atelier de programmation fait partie du cours Principes de base d'Android en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.
Introduction
Dans l'atelier de programmation précédent, vous avez appris à extraire des données d'un service Web et à analyser la réponse dans un objet de données. Dans cet atelier, vous allez vous appuyer sur ces connaissances pour charger et afficher des photos à partir d'une adresse URL. Vous verrez également à nouveau comment créer une RecyclerView et l'utiliser pour afficher une grille d'images sur la page de présentation.
Ce que vous devez déjà savoir
- Vous savez créer et utiliser des fragments.
- Utiliser les composants d'architecture, y compris les ViewModels, les fabriques de ViewModel, les transformations et
LiveData. - Vous savez récupérer des fichiers JSON à partir d'un service Web REST et analyser ces données dans des objets Kotlin à l'aide des bibliothèques Retrofit et Moshi.
- Vous savez créer une mise en page basée sur une grille avec un élément
RecyclerView. - Vous connaissez le fonctionnement des éléments
Adapter,ViewHolderetDiffUtil.
Points abordés
- Comment utiliser la bibliothèque Glide pour charger et afficher une image à partir d'une URL
- Comment utiliser un élément
RecyclerViewet un adaptateur pour afficher une grille d'images - Comment gérer les erreurs potentielles lors du téléchargement et de l'affichage des images
Objectifs de l'atelier
- Modifier l'application MarsRealEstate pour obtenir l'URL de l'image à partir des données de la propriété sur Mars, puis utiliser Glide pour charger et afficher cette image
- Ajouter une animation de chargement et une icône d'erreur à l'application
- Utiliser un élément
RecyclerViewpour afficher une grille d'images de propriétés de Mars. - Ajouter un état et une gestion des erreurs à
RecyclerView.
Dans cet atelier de programmation (et les ateliers associés), vous allez utiliser une application appelée MarsRealEstate, qui affiche les propriétés à vendre sur Mars. L'application se connecte à un serveur Internet pour récupérer et afficher les données des propriétés, y compris des informations telles que le prix et la disponibilité à la vente ou à la location. Les images représentant chaque propriété sont de vraies photos de Mars prises par les rovers de la NASA.

La version de l'application que vous allez créer dans cet atelier de programmation remplit la page de présentation en affichant une grille d'images. Les images font partie des données de propriété que votre application obtient du service Web immobilier Mars. Votre application utilisera la bibliothèque Glide pour charger et afficher les images, ainsi qu'un élément pour les mettre en page.RecyclerView Votre application pourra également gérer correctement les erreurs réseau.
Même si afficher une photo à partir d'une URL peut sembler simple, plusieurs opérations sont nécessaires pour que cela fonctionne correctement. Vous devez la télécharger, la mettre en mémoire tampon et décoder son format compressé pour récupérer une image compatible avec Android. L'image doit être mise en cache dans la mémoire, sur un support de stockage ou les deux. Toutes ces opérations doivent être réalisées dans des threads de faible priorité afin que l'UI reste réactive. Pour optimiser les performances du réseau et du processeur, vous pouvez également extraire et décoder plusieurs images à la fois. Apprendre à charger efficacement des images depuis le réseau pourrait faire l'objet d'un atelier de programmation à part entière.
Heureusement, vous pouvez utiliser Glide, une bibliothèque développée par la communauté, pour télécharger, mettre en mémoire tampon, décoder et mettre en cache vos images. Glide vous épargne beaucoup de travail par rapport à si vous deviez tout faire à partir de zéro.
Glide a besoin de deux choses :
- l'URL de l'image que vous souhaitez charger et afficher ;
- Un objet
ImageViewpour afficher cette image.
Dans cette tâche, vous allez apprendre à utiliser Glide pour afficher une seule image à partir du service Web immobilier. Vous afficherez l'image qui représente la première propriété sur Mars dans la liste renvoyée par le service Web. Voici les captures d'écran avant et après :


Étape 1 : Ajoutez la dépendance Glide
- Ouvrez l'application MarsRealEstate du dernier atelier de programmation. (Vous pouvez télécharger MarsRealEstateNetwork ici si vous n'avez pas l'application.)
- Exécutez l'application pour voir comment elle réagit. (Il affiche les détails textuels d'une propriété hypothétiquement disponible sur Mars.)
- Ouvrez build.gradle (Module: app).
- Dans la section
dependencies, ajoutez la ligne suivante pour la bibliothèque Glide :
implementation "com.github.bumptech.glide:glide:$version_glide"
Notez que le numéro de version est déjà défini séparément dans le fichier Gradle du projet.
- Cliquez sur Sync now (Synchroniser) pour recréer le projet avec la nouvelle dépendance.
Étape 2 : Mettez à jour le modèle de vue
Ensuite, mettez à jour la classe OverviewViewModel pour inclure les données en direct d'une seule propriété Mars.
- Ouvrez
overview/OverviewViewModel.kt. Juste en dessous deLiveDatapour_response, ajoutez des données LiveData internes (modifiables) et externes (immuables) pour un seul objetMarsProperty.
Lorsque vous y êtes invité, importez la classeMarsProperty(com.example.android.marsrealestate.network.MarsProperty).
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property- Dans la méthode
getMarsRealEstateProperties(), recherchez la ligne à l'intérieur du bloctry/catch {}qui définit_response.valuesur le nombre de propriétés. Ajoutez le test indiqué ci-dessous. Si des objetsMarsPropertysont disponibles, ce test définit la valeur deLiveData_propertysur la première propriété delistResult.
if (listResult.size > 0) {
_property.value = listResult[0]
}Le bloc try/catch {} complet ressemble maintenant à ceci :
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}- Ouvrez le fichier
res/layout/fragment_overview.xml. Dans l'élément<TextView>, remplacezandroid:textpar la liaison au composantimgSrcUrldepropertyLiveData:
android:text="@{viewModel.property.imgSrcUrl}"- Exécutez l'application. L'élément
TextViewn'affiche que l'URL de l'image dans la première propriété Mars. Pour l'instant, vous avez configuré le ViewModel et les données LiveData pour cette URL.

Étape 3 : Créez un adaptateur de liaison et appelez Glide
Maintenant que vous disposez de l'URL d'une image à afficher, il est temps de commencer à travailler avec Glide pour charger cette image. Dans cette étape, vous utilisez un adaptateur de liaison pour récupérer l'URL d'un attribut XML associé à un ImageView, et vous utilisez Glide pour charger l'image. Les adaptateurs de liaison sont des méthodes d'extension situées entre une vue et les données liées. Ils permettent d'indiquer un comportement personnalisé en cas de modification des données. Dans ce cas, le comportement personnalisé consiste à appeler Glide pour charger une image à partir d'une URL dans un élément ImageView.
- Ouvrez
BindingAdapters.kt. Ce fichier contiendra les adaptateurs de liaison que vous utilisez dans l'application. - Créez une fonction
bindImage()qui reçoit unImageViewet unStringcomme paramètres. Annotez la fonction avec@BindingAdapter. L'annotation@BindingAdapterindique à la liaison de données que vous souhaitez que cet adaptateur de liaison soit exécuté lorsqu'un élément XML possède l'attributimageUrl.
Importezandroidx.databinding.BindingAdapteretandroid.widget.ImageViewlorsque vous y êtes invité.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}- Dans la fonction
bindImage(), ajoutez un bloclet {}pour l'argumentimgUrl:
imgUrl?.let {
}- Dans le bloc
let {}, ajoutez la ligne ci-dessous pour convertir la chaîne d'URL (à partir du fichier XML) en objetUri. Importezandroidx.core.net.toUrilorsque vous y êtes invité.
Vous souhaitez que l'objetUrifinal utilise le schéma HTTPS, car le serveur à partir duquel vous récupérez les images nécessite ce schéma. Pour utiliser le schéma HTTPS, ajoutezbuildUpon.scheme("https")au compilateurtoUri. La méthodetoUri()est une fonction d'extension Kotlin de la bibliothèque principale Android KTX. Elle ressemble donc à une partie de la classeString.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()- Toujours dans
let {}, appelezGlide.with()pour charger l'image de l'objetUridansImageView. Lorsque vous y êtes invité, importezcom.bumptech.glide.Glide.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)Étape 4 : Mettez à jour la mise en page et les fragments
Bien que Glide ait chargé l'image, il n'y a rien à voir pour le moment. L'étape suivante consiste à mettre à jour la mise en page et les fragments avec un ImageView pour afficher l'image.
- Ouvrez
res/layout/gridview_item.xml. Il s'agit du fichier de ressources de mise en page que vous utiliserez pour chaque élément deRecyclerViewplus tard dans l'atelier de programmation. Vous l'utilisez temporairement ici pour n'afficher qu'une seule image. - Au-dessus de l'élément
<ImageView>, ajoutez un élément<data>pour la liaison de données et liez la classeOverviewViewModel:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>- Ajoutez un attribut
app:imageUrlà l'élémentImageViewpour utiliser le nouvel adaptateur de liaison pour le chargement d'image :
app:imageUrl="@{viewModel.property.imgSrcUrl}"- Ouvrez
overview/OverviewFragment.kt. Dans la méthodeonCreateView(), mettez en commentaire la ligne qui gonfle la classeFragmentOverviewBindinget l'attribue à la variable de liaison. Ce n'est que temporaire. Vous y reviendrez plus tard.
//val binding = FragmentOverviewBinding.inflate(inflater)- Ajoutez une ligne pour gonfler la classe
GridViewItemBinding. Importezcom.example.android.marsrealestate. databinding.GridViewItemBinding, si nécessaire.
val binding = GridViewItemBinding.inflate(inflater)- Exécutez l'application. Vous devriez maintenant voir la photo de l'image du premier
MarsPropertyde la liste des résultats.
Étape 5 : Ajouter des images de chargement et d'erreur simples
Glide peut améliorer l'expérience utilisateur en utilisant une image qui réserve l'espace lors du chargement, ou une image d'erreur en cas d'échec. Cette fonctionnalité est particulièrement utile si l'image est manquante ou corrompue, par exemple. Au cours de cette étape, vous allez ajouter cette fonctionnalité à l'adaptateur de liaison et à la mise en page.
- Ouvrez
res/drawable/ic_broken_image.xml, puis cliquez sur l'onglet Aperçu à droite. Pour l'image d'erreur, nous allons utiliser l'icône d'image défectueuse disponible dans la bibliothèque d'icônes intégrée. Ce drawable vectoriel utilise l'attributandroid:tintpour colorer l'icône en gris.

- Ouvrez
res/drawable/loading_animation.xml. Ce drawable est une animation définie avec le tag<animate-rotate>. L'animation fait pivoter une image drawable,loading_img.xml, autour de son point central. (L'animation ne s'affiche pas dans l'aperçu.)

- Revenez au fichier
BindingAdapters.kt. Dans la méthodebindImage(), mettez à jour l'appel àGlide.with()pour appeler la fonctionapply()entreload()etinto(). Importezcom.bumptech.glide.request.RequestOptionslorsque vous y êtes invité.
Ce code définit l'image de chargement de l'espace réservé à utiliser lors du chargement (le drawableloading_animation). Il définit également une image à utiliser en cas d'échec du chargement (le drawablebroken_image). Une fois terminée, la méthodebindImage()se présente comme suit :
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- Exécutez l'application. Selon la vitesse de votre connexion réseau, vous pouvez voir brièvement l'image de chargement pendant que Glide télécharge et affiche l'image de propriété. Toutefois, pour le moment, l'icône représentant une image défectueuse ne s'affichera pas même si vous désactivez votre réseau. Vous allez résoudre ce problème dans la dernière partie de cet atelier de programmation.
Votre application charge désormais les informations sur les propriétés depuis Internet. En utilisant les données du premier élément de la liste MarsProperty, vous avez créé une propriété LiveData dans le modèle de vue, puis vous avez utilisé l'URL de l'image issue des données de cette propriété pour insérer un élément ImageView. L'objectif étant que votre application affiche une grille d'images, vous allez utiliser un élément RecyclerView avec un GridLayoutManager.
Étape 1 : Mettez à jour le modèle de vue
Pour l'instant, le modèle de vue comporte un _property LiveData qui contient un objet MarsProperty (le premier élément de la liste des réponses du service Web). Au cours de cette étape, vous allez modifier ce LiveData pour qu'il puisse contenir l'ensemble des objets MarsProperty.
- Ouvrez
overview/OverviewViewModel.kt. - Remplacez la variable privée
_propertypar_properties. Modifiez le type pour le définir sur la liste d'objetsMarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()- Remplacez les données actives
propertyexternes parproperties. Ajoutez également la liste au typeLiveData:
val properties: LiveData<List<MarsProperty>>
get() = _properties- Faites défiler la page vers le bas jusqu'à la méthode
getMarsRealEstateProperties(). Dans le bloctry {}, remplacez l'intégralité du test que vous avez ajouté dans la tâche précédente par la ligne ci-dessous. Étant donné que la variablelistResultcontient une liste d'objetsMarsProperty, vous pouvez simplement l'attribuer à_properties.valueau lieu de tester une réponse réussie.
_properties.value = listResultLe bloc try/catch complet ressemble maintenant à ceci :
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}Étape 2 : Mettez à jour les mises en page et les fragments
L'étape suivante consiste à modifier la mise en page et les fragments de l'application pour utiliser une vue de recyclage et une mise en page en grille, plutôt qu'une vue composée d'une seule image.
- Ouvrez
res/layout/gridview_item.xml. Remplacez la liaison de données deOverviewViewModelparMarsProperty, puis renommez la variable en"property".
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />- Dans
<ImageView>, modifiez l'attributapp:imageUrlpour faire référence à l'URL de l'image dans l'objetMarsProperty:
app:imageUrl="@{property.imgSrcUrl}"- Ouvrez
overview/OverviewFragment.kt. DansonCreateview(), annulez la mise en commentaire de la ligne qui gonfleFragmentOverviewBinding. Supprimez ou mettez en commentaire la ligne qui gonfleGridViewBinding. Ces modifications annulent les modifications temporaires apportées lors de la tâche précédente.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)- Ouvrez
res/layout/fragment_overview.xml. Supprimez l'intégralité de l'élément<TextView>. - Ajoutez plutôt cet élément
<RecyclerView>, qui utilise unGridLayoutManageret la mise en pagegrid_view_itempour un seul élément :
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />Étape 3 : Ajouter l'adaptateur pour grille de photos
La mise en page fragment_overview comporte maintenant un élément RecyclerView, tandis que la mise en page grid_view_item comporte un seul élément ImageView. Au cours de cette étape, vous allez faire le lien entre le RecyclerView et les données via un adaptateur RecyclerView.
- Ouvrez
overview/PhotoGridAdapter.kt. - Créez la classe
PhotoGridAdapteravec les paramètres de constructeur indiqués ci-dessous. La classePhotoGridAdapterétendListAdapter, dont le constructeur a besoin d'un élément de type liste, du conteneur de vue et d'une implémentationDiffUtil.ItemCallback.
Si vous y êtes invité, importez les classesandroidx.recyclerview.widget.ListAdapteretcom.example.android.marsrealestate.network.MarsProperty. Dans les étapes suivantes, vous allez ajouter les autres parties manquantes de ce constructeur qui génèrent des erreurs.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}- Cliquez n'importe où dans la classe
PhotoGridAdapteret appuyez surControl+ipour implémenter les méthodesListAdapter, à savoironCreateViewHolder()etonBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}- À la fin de la définition de la classe
PhotoGridAdapter, après les méthodes que vous venez d'ajouter, ajoutez une définition d'objet associé pourDiffCallback, comme indiqué ci-dessous.
Importezandroidx.recyclerview.widget.DiffUtillorsque vous y êtes invité.
L'objetDiffCallbackétendDiffUtil.ItemCallbackavec le type d'objet que vous souhaitez comparer, à savoirMarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}- Appuyez sur
Control+ipour implémenter les méthodes de comparaison pour cet objet, à savoirareItemsTheSame()etareContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }- Pour la méthode
areItemsTheSame(), supprimez l'élément TODO. Utilisez l'opérateur d'égalité référentielle de Kotlin (===), qui renvoietruesi les références d'objet pouroldItemetnewItemsont identiques.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}- Pour
areContentsTheSame(), utilisez l'opérateur d'égalité standard uniquement sur l'ID deoldItemetnewItem.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}- Toujours dans la classe
PhotoGridAdapter, sous l'objet associé, ajoutez une définition de classe interne pourMarsPropertyViewHolder, qui étendRecyclerView.ViewHolder.
Importezandroidx.recyclerview.widget.RecyclerViewetcom.example.android.marsrealestate.databinding.GridViewItemBindinglorsque vous y êtes invité.
Vous avez besoin de la variableGridViewItemBindingpour lierMarsPropertyà la mise en page. Pour ce faire, transmettez-la dansMarsPropertyViewHolder. La classeViewHolderde base nécessitant une vue dans son constructeur, vous lui transmettez la vue racine de liaison.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}- Dans
MarsPropertyViewHolder, créez une méthodebind()qui reçoit un objetMarsPropertycomme argument et définitbinding.propertysur cet objet. Après avoir défini la propriété, appelezexecutePendingBindings(). La mise à jour se lance immédiatement.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}- Dans
onCreateViewHolder(), supprimez l'élément TODO et ajoutez la ligne ci-dessous. Importezandroid.view.LayoutInflater, si nécessaire.
La méthodeonCreateViewHolder()doit renvoyer un nouveauMarsPropertyViewHolder, créé en gonflantGridViewItemBindinget en utilisant leLayoutInflaterde votre contexteViewGroupparent.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))- Dans la méthode
onBindViewHolder(), supprimez l'élément TODO et ajoutez les lignes ci-dessous. Ici, vous appelezgetItem()pour obtenir l'objetMarsPropertyassocié à la position actuelle deRecyclerView, puis vous transmettez cette propriété à la méthodebind()dansMarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)Étape 4 : Ajoutez l'adaptateur de liaison et connectez les composants
Enfin, utilisez BindingAdapter pour initialiser PhotoGridAdapter avec la liste d'objets MarsProperty. Si vous utilisez BindingAdapter pour définir les données de RecyclerView, la liaison de données observe automatiquement LiveData pour trouver la liste des objets MarsProperty. Ensuite, l'adaptateur de liaison est appelé automatiquement lorsque la liste MarsProperty change.
- Ouvrez
BindingAdapters.kt. - À la fin du fichier, ajoutez une méthode
bindRecyclerView()qui accepte un élémentRecyclerViewet une liste d'objetsMarsPropertycomme arguments. Annotez cette méthode avec@BindingAdapter.
Importezandroidx.recyclerview.widget.RecyclerViewetcom.example.android.marsrealestate.network.MarsPropertylorsque vous y êtes invité.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}- Dans la fonction
bindRecyclerView(), convertissezrecyclerView.adapterenPhotoGridAdapteret appelezadapter.submitList()avec les données. Cela permet de prévenir l'élémentRecyclerViewlorsqu'une nouvelle liste est disponible.
Lorsque vous y êtes invité, importez com.example.android.marsrealestate.overview.PhotoGridAdapter.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)- Ouvrez
res/layout/fragment_overview.xml. Ajoutez l'attributapp:listDataà l'élémentRecyclerViewet définissez-le surviewmodel.propertiesà l'aide de la liaison de données.
app:listData="@{viewModel.properties}"- Ouvrez
overview/OverviewFragment.kt. DansonCreateView(), juste avant l'appel àsetHasOptionsMenu(), initialisez l'adaptateurRecyclerViewdansbinding.photosGridsur un nouvel objetPhotoGridAdapter.
binding.photosGrid.adapter = PhotoGridAdapter()- Exécutez l'application. Une grille de
MarsPropertyimages doit s'afficher. Lorsque vous faites défiler la page pour afficher de nouvelles images, l'application affiche l'icône de chargement en cours avant que l'image apparaisse. Si vous activez le mode avion, les images qui n'ont pas encore été chargées s'affichent sous forme d'icônes d'images défectueuses.

Lorsqu'une image ne peut pas être extraite, l'application MarsRealEstate affiche l'icône d'image défectueuse. Mais en l'absence de connexion réseau, l'application affiche un écran vierge.

Ce n'est pas une expérience utilisateur optimale. Dans cette tâche, vous allez ajouter une gestion des erreurs basique dans le but de mieux informer l'utilisateur du problème. Si aucune connexion Internet n'est disponible, l'application devra afficher une icône d'erreur de connexion. Pendant que l'application extrait la liste MarsProperty, elle affiche une animation de chargement.
Étape 1 : Ajouter un état au ViewModel
Pour commencer, vous allez créer un LiveData dans le modèle de vue pour représenter l'état de la requête Web. Trois états sont possibles : chargement en cours, réussite et échec. L'état "chargement en cours" est appliqué lorsque l'application est en attente de données dans l'appel à await().
- Ouvrez
overview/OverviewViewModel.kt. En haut du fichier (après les importations, avant la définition de la classe), ajoutezenumpour représenter tous les états disponibles :
enum class MarsApiStatus { LOADING, ERROR, DONE }- Renommez les définitions de données en direct
_responseinternes et externes dans toute la classeOverviewViewModelen_status. Étant donné que vous avez ajouté la prise en charge de_propertiesLiveDataplus tôt dans cet atelier de programmation, la réponse complète du service Web n'a pas été utilisée. Vous avez besoin d'unLiveDataici pour suivre l'état actuel. Vous pouvez donc simplement renommer les variables existantes.
Modifiez également les types de String en MarsApiStatus..
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status- Faites défiler la page vers le bas jusqu'à la méthode
getMarsRealEstateProperties(), puis mettez également à jour_responseen_status. Remplacez la chaîne"Success"par l'étatMarsApiStatus.DONEet la chaîne"Failure"parMarsApiStatus.ERROR. - Ajoutez un état
MarsApiStatus.LOADINGen haut du bloctry {}, avant l'appel àawait(). Il s'agit de l'état initial appliqué pendant que la coroutine est en cours d'exécution et que l'application est en attente de données. Le bloctry/catch {}complet ressemble maintenant à ceci :
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}- Après l'état d'erreur dans le bloc
catch {}, définissez_propertiesLiveDatasur une liste vide. Cela efface leRecyclerView.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}Étape 2 : Ajoutez un adaptateur de liaison pour l'ImageView de l'état
Vous disposez maintenant d'un état dans le ViewModel, mais il ne s'agit que d'un ensemble d'états. Comment le faire apparaître dans l'application elle-même ? Au cours de cette étape, vous utilisez un élément ImageView connecté à la liaison de données pour afficher les icônes des états de chargement et d'erreur. Lorsque l'état de l'application est "en cours de chargement" ou "erreur", l'élément ImageView doit être visible. Une fois le chargement terminé, l'élément ImageView doit être rendu invisible.
- Ouvrez
BindingAdapters.kt. Ajoutez un adaptateur de liaison nommébindStatus(), qui reçoitImageViewetMarsApiStatuscomme arguments. Lorsque vous y êtes invité, importezcom.example.android.marsrealestate.overview.MarsApiStatus.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}- Ajoutez un
when {}dans la méthodebindStatus()pour passer d'un état à un autre.
when (status) {
}- Dans
when {}, ajoutez un cas pour l'état de chargement (MarsApiStatus.LOADING). Pour cet état, définissezImageViewsur "VISIBLE", puis attribuez-lui l'animation de chargement. Il s'agit du drawable animé que vous avez utilisé pour Glide lors de la tâche précédente. Lorsque vous y êtes invité, importezandroid.view.View.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}- Ajoutez un cas pour l'état d'erreur, à savoir
MarsApiStatus.ERROR. Comme vous l'avez fait pour l'étatLOADING, définissez l'étatImageViewsur "VISIBLE" et réutilisez le drawable d'erreur de connexion.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- Ajoutez un cas pour l'état "réussite", à savoir
MarsApiStatus.DONE. Dans ce cas, vous obtenez une réponse positive. Par conséquent, vous devez désactiver la visibilité de l'ImageViewde l'état pour le masquer.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}Étape 3 : Ajoutez l'ImageView de l'état à la mise en page
- Ouvrez
res/layout/fragment_overview.xml. Sous l'élémentRecyclerView, à l'intérieur deConstraintLayout, ajoutez l'élémentImageViewindiqué ci-dessous.
Cet élémentImageViewprésente les mêmes contraintes queRecyclerView. Cependant, la largeur et la hauteur utilisentwrap_contentpour centrer l'image au lieu de l'agrandir pour remplir l'affichage. Vous pouvez également remarquer l'attributapp:marsApiStatus, qui fait en sorte que la vue appelle votreBindingAdapterlorsque la propriété d'état dans le modèle de vue change.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />- Activez le mode Avion dans votre émulateur ou sur votre appareil pour simuler l'absence de connexion réseau. Compilez et exécutez l'application. L'image d'erreur devrait s'afficher :

- Appuyez sur le bouton "Retour" pour fermer l'application, puis désactivez le mode Avion. Utilisez l'écran "Récents" pour revenir à l'application. Selon la vitesse de votre connexion réseau, une icône de chargement peut s'afficher très brièvement lorsque l'application interroge le service Web, avant que les images s'affichent.
Projet Android Studio : MarsRealEstateGrid
- Pour simplifier le processus de gestion des images, utilisez la bibliothèque Glide pour télécharger, mettre en mémoire tampon, décoder et mettre en cache les images dans votre application.
- Pour charger une image depuis Internet, Glide a besoin de deux éléments : l'URL de l'image et un objet
ImageViewdans lequel insérer l'image. Pour spécifier ces options, utilisez les méthodesload()etinto()avec Glide. - Les adaptateurs de liaison sont des méthodes d'extension situées entre une vue et les données qui lui sont liées. Ils permettent d'indiquer un comportement personnalisé en cas de modification des données, par exemple pour appeler Glide afin de charger une image à partir d'une URL dans un élément
ImageView. - Les adaptateurs de liaison sont des méthodes d'extension annotées avec
@BindingAdapter. - Pour ajouter des options à la requête Glide, utilisez la méthode
apply(). Par exemple, utilisezapply()avecplaceholder()pour spécifier un élément graphique de chargement etapply()avecerror()pour spécifier un élément graphique d'erreur. - Pour produire une grille d'images, utilisez un
RecyclerViewavec unGridLayoutManager. - Pour mettre à jour la liste des propriétés lorsqu'elle est modifiée, utilisez un adaptateur de liaison entre
RecyclerViewet la mise en page.
Cours Udacity :
Documentation pour les développeurs Android :
- Présentation de ViewModel
- Présentation de LiveData
- Coroutines, documentation officielle
- Adaptateurs de liaison
Autre :
Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :
- Attribuer des devoirs si nécessaire
- Indiquer aux élèves comment rendre leurs devoirs
- Noter les devoirs
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.
Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondre aux questions suivantes
Question 1
Quelle méthode Glide permet d'indiquer l'élément ImageView qui contiendra l'image chargée ?
▢ into()
▢ with()
▢ imageview()
▢ apply()
Question 2
Comment spécifier un espace réservé pour une image à afficher lors du chargement de Glide ?
▢ Utilisez la méthode into() avec un drawable.
▢ Utilisez RequestOptions() et appelez la méthode placeholder() avec un drawable.
▢ Attribuez la propriété Glide.placeholder à un drawable.
▢ Utilisez RequestOptions() et appelez la méthode loadingImage() avec un drawable.
Question 3
Comment indiquer qu'une méthode est un adaptateur de liaison ?
▢ Appelez la méthode setBindingAdapter() sur LiveData.
▢ Ajoutez la méthode dans un fichier Kotlin appelé BindingAdapters.kt.
▢ Utilisez l'attribut android:adapter dans la mise en page XML.
▢ Annotez la méthode avec @BindingAdapter.
Passez à la leçon suivante :
Pour obtenir des liens vers d'autres ateliers de programmation de ce cours, consultez la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.