Principes de base d'Android en Kotlin 07.4 : Interagir avec des éléments RecyclerView

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

La plupart des applications qui utilisent des listes et des grilles pour afficher des éléments permettent aux utilisateurs d'interagir avec ces éléments. Appuyer sur un élément d'une liste et afficher ses détails est un cas d'utilisation très courant pour ce type d'interaction. Pour ce faire, vous pouvez ajouter des écouteurs de clic qui répondent aux appuis des utilisateurs sur les éléments en affichant une vue détaillée.

Dans cet atelier de programmation, vous allez ajouter de l'interaction à votre RecyclerView, en vous appuyant sur une version étendue de l'application de suivi du sommeil de la série d'ateliers de programmation précédente.

Ce que vous devez déjà savoir

  • Créer une interface utilisateur de base à l'aide d'une activité, de fragments et de vues.
  • Naviguer entre les fragments et utiliser safeArgs pour transmettre des données entre les fragments.
  • Affichez les modèles, les fabriques de modèles, les transformations et les LiveData, ainsi que leurs observateurs.
  • Création d'une base de données Room, d'un objet d'accès aux données (DAO) et définition d'entités
  • Utiliser des coroutines pour les tâches de base de données et autres tâches de longue durée
  • Implémenter un RecyclerView de base avec une mise en page Adapter, ViewHolder et d'élément.
  • Découvrez comment implémenter la liaison de données pour RecyclerView.
  • Découvrez comment créer et utiliser des adaptateurs de liaison pour transformer des données.
  • Comment utiliser GridLayoutManager

Points abordés

  • Comment rendre les éléments de RecyclerView cliquables. Implémentez un écouteur de clics pour accéder à une vue détaillée lorsqu'un élément est sélectionné.

Objectifs de l'atelier

  • Développez une version étendue de l'application TrackMySleepQuality à partir de l'atelier de programmation précédent de cette série.
  • Ajoutez un écouteur de clics à votre liste et commencez à écouter les interactions de l'utilisateur. Lorsqu'un utilisateur appuie sur un élément de la liste, il est redirigé vers un fragment contenant des informations sur l'élément sélectionné. Le code de démarrage fournit le code du fragment de détail, ainsi que le code de navigation.

L'application de suivi du sommeil de départ comporte deux écrans, représentés par des fragments, comme illustré dans la figure ci-dessous.

Le premier écran, affiché à gauche, comporte des boutons permettant de démarrer et d'arrêter le suivi. L'écran affiche certaines données de sommeil de l'utilisateur. Le bouton Effacer supprime définitivement toutes les données que l'application a collectées pour l'utilisateur. Le deuxième écran, à droite, permet de sélectionner une note de qualité du sommeil.

Cette application utilise une architecture simplifiée avec un contrôleur d'UI, un ViewModel et LiveData, ainsi qu'une base de données Room pour conserver les données de sommeil.

Dans cet atelier de programmation, vous allez ajouter la possibilité de répondre lorsqu'un utilisateur appuie sur un élément de la grille, ce qui affiche un écran de détails comme celui ci-dessous. Le code de cet écran (fragment, modèle de vue et navigation) est fourni avec l'application de démarrage. Vous implémenterez le mécanisme de gestion des clics.

Étape 1 : Télécharger l'application de démarrage

  1. Téléchargez le code de démarrage RecyclerViewClickHandler depuis GitHub et ouvrez le projet dans Android Studio.
  2. Créez et exécutez l'application de suivi du sommeil de départ.

[Facultatif] Mettez à jour votre application si vous souhaitez utiliser celle de l'atelier de programmation précédent.

Si vous prévoyez de travailler à partir de l'application de démarrage fournie dans GitHub pour cet atelier de programmation, passez à l'étape suivante.

Si vous souhaitez continuer à utiliser votre propre application de suivi du sommeil que vous avez créée dans l'atelier de programmation précédent, suivez les instructions ci-dessous pour mettre à jour votre application existante afin qu'elle contienne le code du fragment de l'écran de détails.

  1. Même si vous continuez à utiliser votre application existante, obtenez le code de démarrage RecyclerViewClickHandler sur GitHub pour pouvoir copier les fichiers.
  2. Copiez tous les fichiers du package sleepdetail.
  3. Dans le dossier layout, copiez le fichier fragment_sleep_detail.xml.
  4. Copiez le contenu mis à jour de navigation.xml, qui ajoute la navigation pour sleep_detail_fragment.
  5. Dans le package database, dans SleepDatabaseDao, ajoutez la nouvelle méthode getNightWithId() :
/**
 * Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
  1. Dans res/values/strings, ajoutez la ressource de chaîne suivante :
<string name="close">Close</string>
  1. Nettoyez et recréez votre application pour mettre à jour la liaison de données.

Étape 2 : Inspecter le code de l'écran des détails du sommeil

Dans cet atelier de programmation, vous allez implémenter un gestionnaire de clics qui accède à un fragment affichant des informations sur la nuit de sommeil sur laquelle l'utilisateur a cliqué. Votre code de démarrage contient déjà le fragment et le graphique de navigation pour ce SleepDetailFragment, car il s'agit d'une quantité importante de code, et les fragments et la navigation ne font pas partie de cet atelier de programmation. Familiarisez-vous avec le code suivant :

  1. Dans votre application, recherchez le package sleepdetail. Ce package contient le fragment, le ViewModel et la fabrique de ViewModel pour un fragment qui affiche les détails d'une nuit de sommeil.

  2. Dans le package sleepdetail, ouvrez et examinez le code de SleepDetailViewModel. Ce modèle de vue utilise la clé d'un SleepNight et un DAO dans le constructeur.

    Le corps de la classe contient le code permettant d'obtenir le SleepNight pour la clé donnée et la variable navigateToSleepTracker permettant de contrôler la navigation vers SleepTrackerFragment lorsque l'utilisateur appuie sur le bouton Fermer.

    La fonction getNightWithId() renvoie un LiveData<SleepNight> et est définie dans SleepDatabaseDao (dans le package database).

  3. Dans le package sleepdetail, ouvrez et examinez le code de SleepDetailFragment. Notez la configuration de la liaison de données, du modèle de vue et de l'observateur pour la navigation.

  4. Dans le package sleepdetail, ouvrez et inspectez le code de SleepDetailViewModelFactory.

  5. Dans le dossier de mise en page, inspectez fragment_sleep_detail.xml. Notez la variable sleepDetailViewModel définie dans la balise <data> pour obtenir les données à afficher dans chaque vue à partir du ViewModel.

    La mise en page contient un ConstraintLayout qui contient un ImageView pour la qualité du sommeil, un TextView pour une note de qualité, un TextView pour la durée du sommeil et un Button pour fermer le fragment de détails.

  6. Ouvrez le fichier navigation.xml. Pour sleep_tracker_fragment, notez la nouvelle action pour sleep_detail_fragment.

    La nouvelle action, action_sleep_tracker_fragment_to_sleepDetailFragment, correspond à la navigation du fragment du suivi du sommeil vers l'écran de détails.

Dans cette tâche, vous allez mettre à jour RecyclerView pour répondre aux appuis de l'utilisateur en affichant un écran de détails pour l'élément sélectionné.

La réception et la gestion des clics sont une tâche en deux parties : vous devez d'abord écouter et recevoir le clic, puis déterminer sur quel élément il a été effectué. Vous devez ensuite répondre au clic par une action.

Quel est le meilleur endroit pour ajouter un écouteur de clics pour cette application ?

  • Le SleepTrackerFragment héberge de nombreuses vues. Par conséquent, l'écoute des événements de clic au niveau du fragment ne vous indiquera pas sur quel élément l'utilisateur a cliqué. Il ne vous indiquera même pas s'il s'agissait d'un élément sur lequel l'utilisateur a cliqué ou d'un autre élément d'interface utilisateur.
  • Si vous écoutez au niveau RecyclerView, il est difficile de déterminer exactement sur quel élément de la liste l'utilisateur a cliqué.
  • Le meilleur rythme pour obtenir des informations sur un élément cliqué se trouve dans l'objet ViewHolder, car il représente un élément de liste.

Bien que le ViewHolder soit idéal pour écouter les clics, il n'est généralement pas adapté pour les gérer. Alors, quel est le meilleur endroit pour gérer les clics ?

  • L'Adapter affiche des éléments de données dans des vues afin que vous puissiez gérer les clics. Sa tâche consiste à adapter les données à afficher, pas à traiter la logique de l'application.
  • En général, les clics doivent être gérés dans le ViewModel, car il a accès aux données et à la logique permettant de déterminer comment réagir au clic.ViewModel

Étape 1 : Créez un écouteur de clics et déclenchez-le à partir de la mise en page de l'élément

  1. Dans le dossier sleeptracker, ouvrez SleepNightAdapter.kt.
  2. À la fin du fichier, au niveau supérieur, créez une classe d'écouteur, SleepNightListener.
class SleepNightListener() {
    
}
  1. Dans la classe SleepNightListener, ajoutez une fonction onClick(). Lorsque l'utilisateur clique sur la vue qui affiche un élément de liste, la vue appelle cette fonction onClick(). (Vous définirez ultérieurement la propriété android:onClick de la vue sur cette fonction.)
class SleepNightListener() {
    fun onClick() = 
}
  1. Ajoutez un argument de fonction night de type SleepNight à onClick(). La vue sait quel élément elle affiche, et cette information doit être transmise pour gérer le clic.
class SleepNightListener() {
    fun onClick(night: SleepNight) = 
}
  1. Pour définir ce que fait onClick(), fournissez un rappel clickListener dans le constructeur de SleepNightListener et attribuez-le à onClick().

    Donner un nom au lambda qui gère le clic, clickListener , permet de le suivre lorsqu'il est transmis entre les classes. Le rappel clickListener n'a besoin que de night.nightId pour accéder aux données de la base de données. Votre classe SleepNightListener terminée devrait ressembler au code ci-dessous.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  1. Ouvrez list_item_sleep_night.xml..
  2. Dans le bloc data, ajoutez une variable pour rendre la classe SleepNightListener disponible via la liaison de données. Attribuez à la nouvelle <variable> un name de clickListener.. Définissez type sur le nom complet de la classe com.example.android.trackmysleepquality.sleeptracker.SleepNightListener, comme indiqué ci-dessous. Vous pouvez désormais accéder à la fonction onClick() dans SleepNightListener à partir de cette mise en page.
<variable
            name="clickListener"
            type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
  1. Pour écouter les clics sur n'importe quelle partie de cet élément de liste, ajoutez l'attribut android:onClick à ConstraintLayout.

    Définissez l'attribut sur clickListener:onClick(sleep) à l'aide d'un lambda de liaison de données, comme indiqué ci-dessous :
android:onClick="@{() -> clickListener.onClick(sleep)}"

Étape 2 : Transmettez l'écouteur de clic au support de vue et à l'objet de liaison

  1. Ouvrez SleepNightAdapter.kt.
  2. Modifiez le constructeur de la classe SleepNightAdapter pour recevoir un val clickListener: SleepNightListener. Lorsque l'adaptateur lie ViewHolder, il doit lui fournir cet écouteur de clics.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. Dans onBindViewHolder(), mettez à jour l'appel à holder.bind() pour transmettre également l'écouteur de clics à ViewHolder. Vous obtiendrez une erreur de compilation, car vous avez ajouté un paramètre à l'appel de fonction.
holder.bind(getItem(position)!!, clickListener)
  1. Ajoutez le paramètre clickListener à bind(). Pour ce faire, placez le curseur sur l'erreur, puis appuyez sur Alt+Enter (Windows) ou Option+Enter (Mac) pour la corriger , comme indiqué dans la capture d'écran ci-dessous.

  1. Dans la classe ViewHolder, dans la fonction bind(), attribuez l'écouteur de clics à l'objet binding. Une erreur s'affiche, car vous devez mettre à jour l'objet de liaison.
binding.clickListener = clickListener
  1. Pour mettre à jour la liaison de données, nettoyez et recompilez votre projet. (Vous devrez peut-être également invalider les caches.) Vous avez donc récupéré un écouteur de clics à partir du constructeur de l'adaptateur et l'avez transmis jusqu'au support de vue et à l'objet de liaison.

Étape 3 : Afficher un toast lorsqu'un élément est sélectionné

Vous avez maintenant le code en place pour capturer un clic, mais vous n'avez pas implémenté ce qui se passe lorsqu'un élément de liste est sélectionné. La réponse la plus simple consiste à afficher un toast indiquant nightId lorsqu'un élément est sélectionné. Cela permet de vérifier que, lorsqu'un élément de la liste est sélectionné, le nightId approprié est capturé et transmis.

  1. Ouvrez SleepTrackerFragment.kt.
  2. Dans onCreateView(), recherchez la variable adapter. Notez qu'une erreur s'affiche, car il attend désormais un paramètre d'écouteur de clic.
  3. Définissez un écouteur de clics en transmettant un lambda à SleepNightAdapter. Ce lambda simple affiche simplement un toast indiquant le nightId, comme indiqué ci-dessous. Vous devrez importer Toast. Vous trouverez ci-dessous la définition complète mise à jour.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. Exécutez l'application, appuyez sur des éléments et vérifiez qu'ils affichent un toast avec le nightId approprié. Étant donné que les éléments ont des valeurs nightId croissantes et que l'application affiche la nuit la plus récente en premier, l'élément avec la valeur nightId la plus basse se trouve en bas de la liste.

Dans cette tâche, vous allez modifier le comportement lorsqu'un élément du RecyclerView est sélectionné. Au lieu d'afficher un toast, l'application accède à un fragment de détails qui affiche plus d'informations sur la nuit sélectionnée.

Étape 1 : Naviguer au clic

Au cours de cette étape, au lieu d'afficher un toast, vous allez modifier le lambda de l'écouteur de clics dans onCreateView() de SleepTrackerFragment pour transmettre nightId à SleepTrackerViewModel et déclencher la navigation vers SleepDetailFragment.

Définissez la fonction de gestionnaire de clics :

  1. Ouvrez SleepTrackerViewModel.kt.
  2. Dans SleepTrackerViewModel, vers la fin, définissez la fonction de gestionnaire de clics onSleepNightClicked().
fun onSleepNightClicked(id: Long) {

}
  1. Dans onSleepNightClicked(), déclenchez la navigation en définissant _navigateToSleepDetail sur le id transmis de la nuit de sommeil sur laquelle l'utilisateur a cliqué.
fun onSleepNightClicked(id: Long) {
   _navigateToSleepDetail.value = id
}
  1. Mettre en œuvre _navigateToSleepDetail Comme vous l'avez fait précédemment, définissez un private MutableLiveData pour l'état de navigation. Et un getter public val pour l'accompagner.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
   get() = _navigateToSleepDetail
  1. Définissez la méthode à appeler une fois la navigation de l'application terminée. Appelez-la onSleepDetailNavigated() et définissez sa valeur sur null.
fun onSleepDetailNavigated() {
    _navigateToSleepDetail.value = null
}

Ajoutez le code pour appeler le gestionnaire de clics :

  1. Ouvrez SleepTrackerFragment.kt et faites défiler la page jusqu'au code qui crée l'adaptateur et définit SleepNightListener pour afficher un toast.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. Ajoutez le code suivant sous le toast pour appeler un gestionnaire de clics, onSleepNighClicked(), dans sleepTrackerViewModel lorsqu'un élément est sélectionné. Transmettez l'nightId pour que le ViewModel sache quelle nuit de sommeil récupérer. Un message d'erreur s'affiche, car vous n'avez pas encore défini onSleepNightClicked(). Vous pouvez conserver, commenter ou supprimer le toast, selon vos préférences.
sleepTrackerViewModel.onSleepNightClicked(nightId)

Ajoutez le code pour observer les clics :

  1. Ouvrez SleepTrackerFragment.kt.
  2. Dans onCreateView(), juste au-dessus de la déclaration de manager, ajoutez du code pour observer le nouveau LiveData navigateToSleepDetail. Lorsque navigateToSleepDetail change, accédez à SleepDetailFragment en transmettant night, puis appelez onSleepDetailNavigated(). Comme vous l 'avez déjà fait dans un atelier de programmation précédent, voici le code :
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
            night?.let {
              this.findNavController().navigate(
                        SleepTrackerFragmentDirections
                                .actionSleepTrackerFragmentToSleepDetailFragment(night))
               sleepTrackerViewModel.onSleepDetailNavigated()
            }
        })
  1. Exécutez votre code, cliquez sur un élément et… l'application plante.

Gérez les valeurs nulles dans les adaptateurs de liaison :

  1. Exécutez à nouveau l'application en mode débogage. Appuyez sur un élément, puis filtrez les journaux pour afficher les erreurs. Une trace de pile s'affichera, avec un message semblable à celui ci-dessous.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item

Malheureusement, la trace de la pile n'indique pas clairement où cette erreur est déclenchée. L'un des inconvénients de la liaison de données est qu'elle peut rendre le débogage de votre code plus difficile. L'application plante lorsque vous cliquez sur un élément, et le seul nouveau code sert à gérer le clic.

Toutefois, il s'avère qu'avec ce nouveau mécanisme de gestion des clics, il est désormais possible que les adaptateurs de liaison soient appelés avec une valeur null pour item. En particulier, lorsque l'application démarre, LiveData commence par null. Vous devez donc ajouter des vérifications de valeur nulle à chacun des adaptateurs.

  1. Dans BindingUtils.kt, pour chacun des adaptateurs de liaison, remplacez le type de l'argument item par "nullable" et encapsulez le corps avec item?.let{...}. Par exemple, votre adaptateur pour sleepQualityString se présentera comme suit. Modifiez les autres adaptateurs de la même manière.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
   item?.let {
       text = convertNumericQualityToString(item.sleepQuality, context.resources)
   }
}
  1. Exécutez votre application. Appuyez sur un élément pour ouvrir une vue détaillée.

Projet Android Studio : RecyclerViewClickHandler.

Pour que les éléments d'un RecyclerView réagissent aux clics, associez des écouteurs de clics aux éléments de liste dans le ViewHolder et gérez les clics dans le ViewModel.

Pour que les éléments d'un RecyclerView réagissent à la suite d'un clic, vous devez procéder comme suit :

  • Créez une classe d'écouteur qui prend un lambda et l'attribue à une fonction onClick().
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  • Définissez l'écouteur de clics sur la vue.
android:onClick="@{() -> clickListener.onClick(sleep)}"
  • Transmettez l'écouteur de clics au constructeur de l'adaptateur, dans le support de vue, et ajoutez-le à l'objet de liaison.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
  • Dans le fragment qui affiche la vue Recycler, où vous créez l'adaptateur, définissez un écouteur de clic en transmettant un lambda à l'adaptateur.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
      sleepTrackerViewModel.onSleepNightClicked(nightId)
})
  • Implémentez le gestionnaire de clics dans le ViewModel. Pour les clics sur les éléments de la liste, cela déclenche généralement la navigation vers un fragment de détails.

Cours Udacity :

Documentation pour les développeurs Android :

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

Supposons que votre application contienne un RecyclerView qui affiche les éléments d'une liste de courses. Votre application définit également une classe d'écouteur de clics :

class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
    fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}

Comment rendre ShoppingListItemListener disponible pour la liaison de données ? Sélectionnez une option.

▢ Dans le fichier de mise en page contenant le RecyclerView qui affiche la liste de courses, ajoutez une variable <data> pour ShoppingListItemListener.

▢ Dans le fichier de mise en page qui définit la mise en page d'une seule ligne de la liste de courses, ajoutez une variable <data> pour ShoppingListItemListener.

▢ Dans la classe ShoppingListItemListener, ajoutez une fonction pour activer la liaison de données :

fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}

▢ Dans la classe ShoppingListItemListener, dans la fonction onClick(), ajoutez un appel pour activer la liaison de données :

fun onClick(cartItem: CartItem) = { 
    clickListener(cartItem.itemId)
    dataBindingEnable(true)
}

Question 2

Où faut-il ajouter l'attribut android:onClick pour que les éléments d'un RecyclerView réagissent à la suite d'un clic ? Plusieurs réponses possibles.

▢ Ajoutez-le à <androidx.recyclerview.widget.RecyclerView> dans le fichier de mise en page affichant le RecyclerView.

▢ Ajoutez-le au fichier de mise en page d'un élément de la ligne. Si vous souhaitez que l'ensemble de l'élément soit cliquable, ajoutez-le à la vue parent contenant les éléments de la ligne.

▢ Ajoutez-le au fichier de mise en page d'un élément de la ligne. Si vous souhaitez qu'un seul TextView de l'élément soit cliquable, ajoutez-le à <TextView>.

▢ Ajoutez-le toujours au fichier de mise en page pour MainActivity.

Passez à la leçon suivante : 7.5 : En-têtes dans RecyclerView