Principes de base d'Android en Kotlin 08.2 : Charger et afficher des images depuis Internet

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, ViewHolder et DiffUtil.

Points abordés

  • Comment utiliser la bibliothèque Glide pour charger et afficher une image à partir d'une URL
  • Comment utiliser un élément RecyclerView et 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 RecyclerView pour 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 ImageView pour 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

  1. Ouvrez l'application MarsRealEstate du dernier atelier de programmation. (Vous pouvez télécharger MarsRealEstateNetwork ici si vous n'avez pas l'application.)
  2. 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.)
  3. Ouvrez build.gradle (Module: app).
  4. 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.

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

  1. Ouvrez overview/OverviewViewModel.kt. Juste en dessous de LiveData pour _response, ajoutez des données LiveData internes (modifiables) et externes (immuables) pour un seul objet MarsProperty.

    Lorsque vous y êtes invité, importez la classe MarsProperty (com.example.android.marsrealestate.network.MarsProperty).
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. Dans la méthode getMarsRealEstateProperties(), recherchez la ligne à l'intérieur du bloc try/catch {} qui définit _response.value sur le nombre de propriétés. Ajoutez le test indiqué ci-dessous. Si des objets MarsProperty sont disponibles, ce test définit la valeur de LiveData _property sur la première propriété de listResult.
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}"
 }
  1. Ouvrez le fichier res/layout/fragment_overview.xml. Dans l'élément <TextView>, remplacez android:text par la liaison au composant imgSrcUrl de property LiveData :
android:text="@{viewModel.property.imgSrcUrl}"
  1. Exécutez l'application. L'élément TextView n'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.

  1. Ouvrez BindingAdapters.kt. Ce fichier contiendra les adaptateurs de liaison que vous utilisez dans l'application.
  2. Créez une fonction bindImage() qui reçoit un ImageView et un String comme paramètres. Annotez la fonction avec @BindingAdapter. L'annotation @BindingAdapter indique à la liaison de données que vous souhaitez que cet adaptateur de liaison soit exécuté lorsqu'un élément XML possède l'attribut imageUrl.

    Importez androidx.databinding.BindingAdapter et android.widget.ImageView lorsque vous y êtes invité.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. Dans la fonction bindImage(), ajoutez un bloc let {} pour l'argument imgUrl :
imgUrl?.let { 
}
  1. Dans le bloc let {}, ajoutez la ligne ci-dessous pour convertir la chaîne d'URL (à partir du fichier XML) en objet Uri. Importez androidx.core.net.toUri lorsque vous y êtes invité.

    Vous souhaitez que l'objet Uri final 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, ajoutez buildUpon.scheme("https") au compilateur toUri. La méthode toUri() est une fonction d'extension Kotlin de la bibliothèque principale Android KTX. Elle ressemble donc à une partie de la classe String.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. Toujours dans let {}, appelez Glide.with() pour charger l'image de l'objet Uri dans ImageView. Lorsque vous y êtes invité, importez com.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.

  1. Ouvrez res/layout/gridview_item.xml. Il s'agit du fichier de ressources de mise en page que vous utiliserez pour chaque élément de RecyclerView plus tard dans l'atelier de programmation. Vous l'utilisez temporairement ici pour n'afficher qu'une seule image.
  2. Au-dessus de l'élément <ImageView>, ajoutez un élément <data> pour la liaison de données et liez la classe OverviewViewModel :
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. Ajoutez un attribut app:imageUrl à l'élément ImageView pour utiliser le nouvel adaptateur de liaison pour le chargement d'image :
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Ouvrez overview/OverviewFragment.kt. Dans la méthode onCreateView(), mettez en commentaire la ligne qui gonfle la classe FragmentOverviewBinding et l'attribue à la variable de liaison. Ce n'est que temporaire. Vous y reviendrez plus tard.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Ajoutez une ligne pour gonfler la classe GridViewItemBinding. Importez com.example.android.marsrealestate. databinding.GridViewItemBinding, si nécessaire.
val binding = GridViewItemBinding.inflate(inflater)
  1. Exécutez l'application. Vous devriez maintenant voir la photo de l'image du premier MarsProperty de 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.

  1. 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'attribut android:tint pour colorer l'icône en gris.

  1. 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.)

  1. Revenez au fichier BindingAdapters.kt. Dans la méthode bindImage(), mettez à jour l'appel à Glide.with() pour appeler la fonction apply() entre load() et into(). Importez com.bumptech.glide.request.RequestOptions lorsque vous y êtes invité.

    Ce code définit l'image de chargement de l'espace réservé à utiliser lors du chargement (le drawable loading_animation). Il définit également une image à utiliser en cas d'échec du chargement (le drawable broken_image). Une fois terminée, la méthode bindImage() 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)
    }
}
  1. 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.

  1. Ouvrez overview/OverviewViewModel.kt.
  2. Remplacez la variable privée _property par _properties. Modifiez le type pour le définir sur la liste d'objets MarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Remplacez les données actives property externes par properties. Ajoutez également la liste au type LiveData :
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. Faites défiler la page vers le bas jusqu'à la méthode getMarsRealEstateProperties(). Dans le bloc try {}, 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 variable listResult contient une liste d'objets MarsProperty, vous pouvez simplement l'attribuer à _properties.value au lieu de tester une réponse réussie.
_properties.value = listResult

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

  1. Ouvrez res/layout/gridview_item.xml. Remplacez la liaison de données de OverviewViewModel par MarsProperty, puis renommez la variable en "property".
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. Dans <ImageView>, modifiez l'attribut app:imageUrl pour faire référence à l'URL de l'image dans l'objet MarsProperty :
app:imageUrl="@{property.imgSrcUrl}"
  1. Ouvrez overview/OverviewFragment.kt. Dans onCreateview(), annulez la mise en commentaire de la ligne qui gonfle FragmentOverviewBinding. Supprimez ou mettez en commentaire la ligne qui gonfle GridViewBinding. 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)
  1. Ouvrez res/layout/fragment_overview.xml. Supprimez l'intégralité de l'élément <TextView>.
  2. Ajoutez plutôt cet élément <RecyclerView>, qui utilise un GridLayoutManager et la mise en page grid_view_item pour 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.

  1. Ouvrez overview/PhotoGridAdapter.kt.
  2. Créez la classe PhotoGridAdapter avec les paramètres de constructeur indiqués ci-dessous. La classe PhotoGridAdapter étend ListAdapter, dont le constructeur a besoin d'un élément de type liste, du conteneur de vue et d'une implémentation DiffUtil.ItemCallback.

    Si vous y êtes invité, importez les classes androidx.recyclerview.widget.ListAdapter et com.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) {

}
  1. Cliquez n'importe où dans la classe PhotoGridAdapter et appuyez sur Control+i pour implémenter les méthodes ListAdapter, à savoir onCreateViewHolder() et onBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. À 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é pour DiffCallback, comme indiqué ci-dessous.

    Importez androidx.recyclerview.widget.DiffUtil lorsque vous y êtes invité.

    L'objet DiffCallback étend DiffUtil.ItemCallback avec le type d'objet que vous souhaitez comparer, à savoir MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Appuyez sur Control+i pour implémenter les méthodes de comparaison pour cet objet, à savoir areItemsTheSame() et areContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. Pour la méthode areItemsTheSame(), supprimez l'élément TODO. Utilisez l'opérateur d'égalité référentielle de Kotlin (===), qui renvoie true si les références d'objet pour oldItem et newItem sont identiques.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. Pour areContentsTheSame(), utilisez l'opérateur d'égalité standard uniquement sur l'ID de oldItem et newItem.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. Toujours dans la classe PhotoGridAdapter, sous l'objet associé, ajoutez une définition de classe interne pour MarsPropertyViewHolder, qui étend RecyclerView.ViewHolder.

    Importez androidx.recyclerview.widget.RecyclerView et com.example.android.marsrealestate.databinding.GridViewItemBinding lorsque vous y êtes invité.

    Vous avez besoin de la variable GridViewItemBinding pour lier MarsProperty à la mise en page. Pour ce faire, transmettez-la dans MarsPropertyViewHolder. La classe ViewHolder de 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) {

}
  1. Dans MarsPropertyViewHolder, créez une méthode bind() qui reçoit un objet MarsProperty comme argument et définit binding.property sur cet objet. Après avoir défini la propriété, appelez executePendingBindings(). La mise à jour se lance immédiatement.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. Dans onCreateViewHolder(), supprimez l'élément TODO et ajoutez la ligne ci-dessous. Importez android.view.LayoutInflater, si nécessaire.

    La méthode onCreateViewHolder() doit renvoyer un nouveau MarsPropertyViewHolder, créé en gonflant GridViewItemBinding et en utilisant le LayoutInflater de votre contexte ViewGroup parent.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. Dans la méthode onBindViewHolder(), supprimez l'élément TODO et ajoutez les lignes ci-dessous. Ici, vous appelez getItem() pour obtenir l'objet MarsProperty associé à la position actuelle de RecyclerView, puis vous transmettez cette propriété à la méthode bind() dans MarsPropertyViewHolder.
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.

  1. Ouvrez BindingAdapters.kt.
  2. À la fin du fichier, ajoutez une méthode bindRecyclerView() qui accepte un élément RecyclerView et une liste d'objets MarsProperty comme arguments. Annotez cette méthode avec @BindingAdapter.

    Importez androidx.recyclerview.widget.RecyclerView et com.example.android.marsrealestate.network.MarsProperty lorsque vous y êtes invité.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. Dans la fonction bindRecyclerView(), convertissez recyclerView.adapter en PhotoGridAdapter et appelez adapter.submitList() avec les données. Cela permet de prévenir l'élément RecyclerView lorsqu'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)
  1. Ouvrez res/layout/fragment_overview.xml. Ajoutez l'attribut app:listData à l'élément RecyclerView et définissez-le sur viewmodel.properties à l'aide de la liaison de données.
app:listData="@{viewModel.properties}"
  1. Ouvrez overview/OverviewFragment.kt. Dans onCreateView(), juste avant l'appel à setHasOptionsMenu(), initialisez l'adaptateur RecyclerView dans binding.photosGrid sur un nouvel objet PhotoGridAdapter.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Exécutez l'application. Une grille de MarsProperty images 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().

  1. Ouvrez overview/OverviewViewModel.kt. En haut du fichier (après les importations, avant la définition de la classe), ajoutez enum pour représenter tous les états disponibles :
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Renommez les définitions de données en direct _response internes et externes dans toute la classe OverviewViewModel en _status. Étant donné que vous avez ajouté la prise en charge de _properties LiveData plus 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'un LiveData ici 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
  1. Faites défiler la page vers le bas jusqu'à la méthode getMarsRealEstateProperties(), puis mettez également à jour _response en _status. Remplacez la chaîne "Success" par l'état MarsApiStatus.DONE et la chaîne "Failure" par MarsApiStatus.ERROR.
  2. Ajoutez un état MarsApiStatus.LOADING en haut du bloc try {}, 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 bloc try/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
}
  1. Après l'état d'erreur dans le bloc catch {}, définissez _properties LiveData sur une liste vide. Cela efface le RecyclerView.
} 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.

  1. Ouvrez BindingAdapters.kt. Ajoutez un adaptateur de liaison nommé bindStatus(), qui reçoit ImageView et MarsApiStatus comme arguments. Lorsque vous y êtes invité, importez com.example.android.marsrealestate.overview.MarsApiStatus.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Ajoutez un when {} dans la méthode bindStatus() pour passer d'un état à un autre.
when (status) {

}
  1. Dans when {}, ajoutez un cas pour l'état de chargement (MarsApiStatus.LOADING). Pour cet état, définissez ImageView sur "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é, importez android.view.View.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Ajoutez un cas pour l'état d'erreur, à savoir MarsApiStatus.ERROR. Comme vous l'avez fait pour l'état LOADING, définissez l'état ImageView sur "VISIBLE" et réutilisez le drawable d'erreur de connexion.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. 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'ImageView de l'état pour le masquer.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Étape 3 : Ajoutez l'ImageView de l'état à la mise en page

  1. Ouvrez res/layout/fragment_overview.xml. Sous l'élément RecyclerView, à l'intérieur de ConstraintLayout, ajoutez l'élément ImageView indiqué ci-dessous.

    Cet élément ImageView présente les mêmes contraintes que RecyclerView. Cependant, la largeur et la hauteur utilisent wrap_content pour centrer l'image au lieu de l'agrandir pour remplir l'affichage. Vous pouvez également remarquer l'attribut app:marsApiStatus, qui fait en sorte que la vue appelle votre BindingAdapter lorsque 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}" />
  1. 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 :

  1. 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 ImageView dans lequel insérer l'image. Pour spécifier ces options, utilisez les méthodes load() et into() 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, utilisez apply() avec placeholder() pour spécifier un élément graphique de chargement et apply() avec error() pour spécifier un élément graphique d'erreur.
  • Pour produire une grille d'images, utilisez un RecyclerView avec un GridLayoutManager.
  • Pour mettre à jour la liste des propriétés lorsqu'elle est modifiée, utilisez un adaptateur de liaison entre RecyclerView et la mise en page.

Cours Udacity :

Documentation pour les développeurs Android :

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 : 8.3 Filtrer et détailler des vues à l'aide de données Internet

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.