Principes de base d'Android en Kotlin 08.3 Filtrer et détailler des vues à l'aide de données 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 les ateliers de programmation précédents de cette leçon, vous avez appris à obtenir des données sur l'immobilier sur Mars à partir d'un service Web, et à créer un RecyclerView avec une mise en page en grille pour charger et afficher les images à partir de ces données. Dans cet atelier de programmation, vous allez terminer l'application MarsRealEstate en implémentant la possibilité de filtrer les propriétés sur Mars selon qu'elles sont disponibles à la location ou à la vente. Vous allez également créer une vue détaillée pour que l'utilisateur puisse voir les détails d'une propriété s'il appuie sur sa photo dans la vue d'ensemble.

Ce que vous devez déjà savoir

  • Vous savez créer et utiliser des fragments.
  • Naviguer entre les fragments et utiliser Safe Args (un plug-in Gradle) pour transmettre des données entre les fragments
  • Utiliser les composants d'architecture, y compris les ViewModels, les fabriques de ViewModel, les transformations et LiveData.
  • Vous savez récupérer des données encodées au format JSON à partir d'un service Web REST et analyser ces données dans des objets Kotlin à l'aide des bibliothèques Retrofit et Moshi.

Points abordés

  • Comment utiliser des expressions de liaison complexes dans vos fichiers de mise en page.
  • Envoyer des requêtes Retrofit à un service Web en utilisant des options de requête.

Objectifs de l'atelier

  • Modifiez l'application MarsRealEstate pour marquer les propriétés à vendre sur Mars (par rapport à celles à louer) avec une icône en forme de dollar.
  • Utilisez le menu d'options sur la page "Vue d'ensemble" pour créer une requête de service Web qui filtre les propriétés sur Mars par type.
  • Créez un fragment de détails pour une propriété Mars, connectez ce fragment à la grille d'aperçu avec la navigation et transmettez les données de la propriété à ce fragment.

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. Cette application se connecte à un serveur Internet pour récupérer et afficher les données de propriété, y compris des informations telles que le prix et si la propriété est disponible à 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. Dans les ateliers de programmation précédents, vous avez créé un RecyclerView avec une mise en page en grille pour toutes les photos de propriétés :

Dans cette version de l'application, vous allez travailler avec le type de propriété (location ou achat) et ajouter une icône à la mise en page en grille pour marquer les propriétés à vendre :

Modifiez le menu d'options de l'application pour filtrer la grille et n'afficher que les propriétés à louer ou à vendre :

Enfin, vous allez créer une vue détaillée pour une propriété individuelle et connecter les icônes de la grille d'aperçu à ce fragment de détail avec la navigation :

Jusqu'à présent, la seule partie des données de propriété de Mars que vous avez utilisée est l'URL de l'image de la propriété. Toutefois, les données de propriété (que vous avez définies dans la classe MarsProperty) incluent également un ID, un prix et un type (location ou vente). Pour vous rafraîchir la mémoire, voici un extrait des données JSON que vous obtenez du service Web :

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

Dans cette tâche, vous allez commencer à travailler avec le type de propriété Mars pour ajouter une image de signe dollar aux propriétés à vendre sur la page de présentation.

Étape 1 : Mettez à jour MarsProperty pour inclure le type

La classe MarsProperty définit la structure de données pour chaque propriété fournie par le service Web. Dans un atelier de programmation précédent, vous avez utilisé la bibliothèque Moshi pour analyser la réponse JSON brute du service Web Mars dans des objets de données MarsProperty individuels.

Au cours de cette étape, vous allez ajouter une logique à la classe MarsProperty pour indiquer si une propriété est à louer ou non (c'est-à-dire si le type est la chaîne "rent" ou "buy"). Vous utiliserez cette logique à plusieurs endroits. Il est donc préférable de l'avoir ici dans la classe de données plutôt que de la répliquer.

  1. Ouvrez l'application MarsRealEstate du dernier atelier de programmation. (Vous pouvez télécharger MarsRealEstateGrid si vous n'avez pas l'application.)
  2. Ouvrez network/MarsProperty.kt. Ajoutez un corps à la définition de classe MarsProperty, puis ajoutez un getter personnalisé pour isRental qui renvoie true si l'objet est de type "rent".
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

Étape 2 : Mettez à jour la mise en page de l'élément de grille

Vous allez maintenant mettre à jour la mise en page des éléments pour que la grille d'images n'affiche un élément graphique en forme de signe dollar que sur les images de propriétés à vendre :

Avec les expressions de liaison de données, vous pouvez effectuer ce test entièrement dans la mise en page XML des éléments de la grille.

  1. Ouvrez res/layout/grid_view_item.xml. Il s'agit du fichier de mise en page pour chaque cellule individuelle de la mise en page en grille pour RecyclerView. Actuellement, le fichier ne contient que l'élément <ImageView> pour l'image de la propriété.
  2. Dans l'élément <data>, ajoutez un élément <import> pour la classe View. Vous utilisez des importations lorsque vous souhaitez utiliser des composants d'une classe à l'intérieur d'une expression de liaison de données dans un fichier de mise en page. Dans ce cas, vous allez utiliser les constantes View.GONE et View.VISIBLE. Vous devez donc avoir accès à la classe View.
<import type="android.view.View"/>
  1. Entourez l'intégralité de la vue d'image avec un FrameLayout pour permettre à l'élément drawable du signe dollar d'être empilé au-dessus de l'image de la propriété.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. Pour ImageView, remplacez l'attribut android:layout_height par match_parent afin de remplir le nouveau FrameLayout parent.
android:layout_height="match_parent"
  1. Ajoutez un deuxième élément <ImageView> juste en dessous du premier, à l'intérieur de FrameLayout. Utilisez la définition ci-dessous. Cette image apparaît en bas à droite de l'élément de grille, au-dessus de l'image de Mars, et utilise le drawable défini dans res/drawable/ic_for_sale_outline.xml pour l'icône du signe dollar.
<ImageView
   android:id="@+id/mars_property_type"
   android:layout_width="wrap_content"
   android:layout_height="45dp"
   android:layout_gravity="bottom|end"
   android:adjustViewBounds="true"
   android:padding="5dp"
   android:scaleType="fitCenter"
   android:src="@drawable/ic_for_sale_outline"
   tools:src="@drawable/ic_for_sale_outline"/>
  1. Ajoutez l'attribut android:visibility à la vue d'image mars_property_type. Utilisez une expression de liaison pour tester le type de propriété et attribuer la visibilité à View.GONE (pour une location) ou View.VISIBLE (pour un achat).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Jusqu'à présent, vous n'avez vu que des expressions de liaison dans les mises en page qui utilisent des variables individuelles définies dans l'élément <data>. Les expressions de liaison sont extrêmement puissantes et vous permettent d'effectuer des opérations telles que des tests et des calculs mathématiques entièrement dans votre mise en page XML. Dans ce cas, vous utilisez l'opérateur ternaire (?:) pour effectuer un test (cet objet est-il une location ?). Vous fournissez un résultat pour "true" (masquez l'icône en forme de signe dollar avec View.GONE) et un autre pour "false" (affichez cette icône avec View.VISIBLE).

Le nouveau fichier grid_view_item.xml complet est présenté ci-dessous :

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools">
   <data>
       <import type="android.view.View"/>
       <variable
           name="property"
           type="com.example.android.marsrealestate.network.MarsProperty" />
   </data>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="170dp">

       <ImageView
           android:id="@+id/mars_image"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:scaleType="centerCrop"
           android:adjustViewBounds="true"
           android:padding="2dp"
           app:imageUrl="@{property.imgSrcUrl}"
           tools:src="@tools:sample/backgrounds/scenic"/>

       <ImageView
           android:id="@+id/mars_property_type"
           android:layout_width="wrap_content"
           android:layout_height="45dp"
           android:layout_gravity="bottom|end"
           android:adjustViewBounds="true"
           android:padding="5dp"
           android:scaleType="fitCenter"
           android:src="@drawable/ic_for_sale_outline"
           android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
           tools:src="@drawable/ic_for_sale_outline"/>
   </FrameLayout>
</layout>
  1. Compilez et exécutez l'application. Notez que les propriétés qui ne sont pas des locations sont associées à l'icône du signe dollar.

Actuellement, votre application affiche toutes les propriétés Mars dans la grille d'aperçu. Si un utilisateur recherche une propriété locative sur Mars, il lui sera utile de voir des icônes indiquant lesquelles des propriétés disponibles sont à vendre. Toutefois, il devra toujours faire défiler la page pour parcourir les nombreuses propriétés. Dans cette tâche, vous allez ajouter un menu d'options au fragment d'aperçu qui permet à l'utilisateur d'afficher uniquement les locations, uniquement les biens à vendre ou tous les biens.

Pour ce faire, vous pouvez tester le type de chaque MarsProperty dans la grille "Vue d'ensemble" et n'afficher que les propriétés correspondantes. Toutefois, le service Web Mars réel dispose d'un paramètre ou d'une option de requête (appelé filter) qui vous permet d'obtenir uniquement les propriétés de type rent ou buy. Vous pouvez utiliser cette requête de filtre avec l'URL du service Web realestate dans un navigateur comme suit :

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

Dans cette tâche, vous allez modifier la classe MarsApiService pour ajouter une option de requête à la requête de service Web avec Retrofit. Ensuite, vous connectez le menu d'options pour télécharger à nouveau toutes les données des propriétés sur Mars à l'aide de cette option de requête. Étant donné que la réponse que vous obtenez du service Web ne contient que les propriétés qui vous intéressent, vous n'avez pas besoin de modifier la logique d'affichage de la vue pour la grille d'aperçu.

Étape 1 : Mettez à jour le service de l'API Mars

Pour modifier la requête, vous devez revenir à la classe MarsApiService que vous avez implémentée dans le premier atelier de programmation de cette série. Vous modifiez la classe pour fournir une API de filtrage.

  1. Ouvrez network/MarsApiService.kt. Juste en dessous des importations, créez un enum appelé MarsApiFilter pour définir des constantes correspondant aux valeurs de requête attendues par le service Web.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Modifiez la méthode getProperties() pour qu'elle accepte une entrée de chaîne pour la requête de filtre, et annotez cette entrée avec @Query("filter"), comme indiqué ci-dessous.

    Importez retrofit2.http.Query lorsque vous y êtes invité.

    L'annotation @Query indique à la méthode getProperties() (et donc à Retrofit) d'envoyer la requête de service Web avec l'option de filtre. Chaque fois que getProperties() est appelé, l'URL de la requête inclut la partie ?filter=type, qui indique au service Web de répondre avec des résultats correspondant à cette requête.
fun getProperties(@Query("filter") type: String):  

Étape 2 : Mettez à jour le modèle de vue d'ensemble

Vous demandez des données à partir de MarsApiService dans la méthode getMarsRealEstateProperties() dans OverviewViewModel. Vous devez maintenant mettre à jour cette requête pour qu'elle accepte l'argument de filtre.

  1. Ouvrez overview/OverviewViewModel.kt. Vous verrez des erreurs dans Android Studio en raison des modifications que vous avez apportées à l'étape précédente. Ajoutez MarsApiFilter (l'enum des valeurs de filtre possibles) en tant que paramètre à l'appel getMarsRealEstateProperties().

    Importez com.example.android.marsrealestate.network.MarsApiFilter lorsque vous y êtes invité.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Modifiez l'appel à getProperties() dans le service Retrofit pour transmettre cette requête de filtre sous forme de chaîne.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. Dans le bloc init {}, transmettez MarsApiFilter.SHOW_ALL en tant qu'argument à getMarsRealEstateProperties() pour afficher toutes les propriétés lors du premier chargement de l'application.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. À la fin de la classe, ajoutez une méthode updateFilter() qui accepte un argument MarsApiFilter et appelle getMarsRealEstateProperties() avec cet argument.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Étape 3 : Connectez le fragment au menu d'options

La dernière étape consiste à connecter le menu à la fragment pour appeler updateFilter() sur le ViewModel lorsque l'utilisateur sélectionne une option de menu.

  1. Ouvrez res/menu/overflow_menu.xml. L'application MarsRealEstate dispose d'un menu à trois points existant qui propose trois options : afficher toutes les propriétés, afficher uniquement les locations et afficher uniquement les propriétés à vendre.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@+id/show_all_menu"
       android:title="@string/show_all" />
   <item
       android:id="@+id/show_rent_menu"
       android:title="@string/show_rent" />
   <item
       android:id="@+id/show_buy_menu"
       android:title="@string/show_buy" />
</menu>
  1. Ouvrez overview/OverviewFragment.kt. À la fin de la classe, implémentez la méthode onOptionsItemSelected() pour gérer les sélections d'éléments de menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. Dans onOptionsItemSelected(), appelez la méthode updateFilter() sur le modèle de vue avec le filtre approprié. Utilisez un bloc when {} Kotlin pour basculer entre les options. Utilisez MarsApiFilter.SHOW_ALL pour la valeur de filtre par défaut. Renvoie true, car vous avez géré l'élément de menu. Lorsque vous y êtes invité, importez MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter). La méthode onOptionsItemSelected() complète est présentée ci-dessous.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   viewModel.updateFilter(
           when (item.itemId) {
               R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
               R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
               else -> MarsApiFilter.SHOW_ALL
           }
   )
   return true
}
  1. Compilez et exécutez l'application. L'application lance la première grille de présentation avec tous les types de propriétés et les propriétés à vendre marquées avec l'icône dollar.
  2. Sélectionnez Louer dans le menu d'options. Les propriétés sont rechargées et aucune d'elles n'est associée à l'icône Dollar. (Seuls les biens locatifs sont affichés.) Vous devrez peut-être patienter quelques instants pour que l'affichage s'actualise et n'affiche que les propriétés filtrées.
  3. Sélectionnez Acheter dans le menu d'options. Les propriétés sont à nouveau rechargées et s'affichent toutes avec l'icône dollar. (Seules les propriétés à vendre sont affichées.)

Vous disposez à présent d'une grille d'icônes défilante pour les propriétés Mars, mais il est temps d'obtenir plus de détails. Dans cette tâche, vous allez ajouter un fragment de détails pour afficher les détails d'une propriété spécifique. Le fragment de détails affiche une image plus grande, le prix et le type de propriété (location ou vente).

Ce fragment est lancé lorsque l'utilisateur appuie sur une image dans la grille d'aperçu. Pour ce faire, vous devez ajouter un écouteur onClick aux éléments de grille RecyclerView, puis accéder au nouveau fragment. Vous naviguez en déclenchant un changement LiveData dans ViewModel, comme vous l'avez fait tout au long de ces leçons. Vous utilisez également le plug-in Safe Args du composant Navigation pour transmettre les informations MarsProperty sélectionnées du fragment de présentation au fragment de détails.

Étape 1 : Créez le modèle de vue détaillée et mettez à jour la mise en page détaillée

Comme pour le ViewModel et les fragments de la vue d'ensemble, vous devez maintenant implémenter le ViewModel et les fichiers de mise en page pour le fragment d'informations.

  1. Ouvrez detail/DetailViewModel.kt. Tout comme les fichiers Kotlin liés au réseau sont contenus dans le dossier network et les fichiers de présentation dans overview, le dossier detail contient les fichiers associés à la vue détaillée. Notez que la classe DetailViewModel (vide pour le moment) accepte un marsProperty comme paramètre dans le constructeur.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. Dans la définition de la classe, ajoutez LiveData pour la propriété Mars sélectionnée afin d'exposer ces informations dans la vue détaillée. Suivez le schéma habituel de création d'un MutableLiveData pour contenir le MarsProperty lui-même, puis exposez une propriété LiveData publique immuable.

     Importez androidx.lifecycle.LiveData et androidx.lifecycle.MutableLiveData lorsque vous y êtes invité.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Créez un bloc init {} et définissez la valeur de la propriété Mars sélectionnée avec l'objet MarsProperty du constructeur.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Ouvrez res/layout/fragment_detail.xml et examinez-le dans la vue Conception.

    Il s'agit du fichier de mise en page du fragment de détail. Il contient un ImageView pour la grande photo, un TextView pour le type de propriété (location ou vente) et un TextView pour le prix. Notez que la mise en page des contraintes est encapsulée dans un ScrollView. Elle défilera donc automatiquement si la vue devient trop grande pour l'écran, par exemple lorsque l'utilisateur la consulte en mode paysage.
  2. Accédez à l'onglet Texte de la mise en page. En haut de la mise en page, juste avant l'élément <ScrollView>, ajoutez un élément <data> pour associer le ViewModel de détails à la mise en page.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Ajoutez l'attribut app:imageUrl à l'élément ImageView. Définissez-le sur imgSrcUrl à partir de la propriété sélectionnée du modèle de vue.

    L'adaptateur de liaison qui charge une image à l'aide de Glide sera également utilisé ici automatiquement, car cet adaptateur surveille tous les attributs app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Étape 2 : Définissez la navigation dans le modèle de vue d'ensemble

Lorsque l'utilisateur appuie sur une photo dans le modèle d'aperçu, il doit être redirigé vers un fragment affichant des informations sur l'élément sélectionné.

  1. Ouvrez overview/OverviewViewModel.kt. Ajoutez une propriété _navigateToSelectedProperty MutableLiveData et exposez-la avec un LiveData immuable.

    Lorsque ce LiveData passe à une valeur non nulle, la navigation est déclenchée. (Vous ajouterez bientôt le code pour observer cette variable et déclencher la navigation.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. À la fin de la classe, ajoutez une méthode displayPropertyDetails() qui définit _navigateToSelectedProperty sur la propriété Mars sélectionnée.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Ajoutez une méthode displayPropertyDetailsComplete() qui définit la valeur de _navigateToSelectedProperty sur "null". Vous en avez besoin pour marquer l'état de navigation comme terminé et pour éviter que la navigation ne soit déclenchée à nouveau lorsque l'utilisateur revient de la vue détaillée.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Étape 3 : Configurez les écouteurs de clics dans l'adaptateur de grille et le fragment

  1. Ouvrez overview/PhotoGridAdapter.kt. À la fin de la classe, créez une classe OnClickListener personnalisée qui accepte un lambda avec un paramètre marsProperty. Dans la classe, définissez une fonction onClick() définie sur le paramètre lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Faites défiler la page jusqu'à la définition de la classe PhotoGridAdapter et ajoutez une propriété OnClickListener privée au constructeur.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Rendez une photo cliquable en ajoutant onClickListener à l'élément de grille dans la méthode onBindviewHolder(). Définissez l'écouteur de clics entre les appels à getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Ouvrez overview/OverviewFragment.kt. Dans la méthode onCreateView(), remplacez la ligne qui initialise la propriété binding.photosGrid.adapter par celle indiquée ci-dessous.

    Ce code ajoute l'objet PhotoGridAdapter.onClickListener au constructeur PhotoGridAdapter et appelle viewModel.displayPropertyDetails() avec l'objet MarsProperty transmis. Cela déclenche le LiveData dans le modèle de vue pour la navigation.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Étape 4 : Modifiez le graphique de navigation et rendez MarsProperty sérialisable

Lorsqu'un utilisateur appuie sur une photo dans la grille d'aperçu, l'application doit accéder au fragment de détails et transmettre les détails de la propriété Mars sélectionnée afin que la vue détaillée puisse afficher ces informations.

Pour l'instant, vous disposez d'un écouteur de clics de PhotoGridAdapter pour gérer l'appui et d'un moyen de déclencher la navigation à partir du ViewModel. Toutefois, aucun objet MarsProperty n'est encore transmis au fragment de détails. Pour cela, vous utilisez Safe Args du composant Navigation.

  1. Ouvrez res/navigation/nav_graph.xml. Cliquez sur l'onglet Texte pour afficher le code XML du graphique de navigation.
  2. Dans l'élément <fragment> du fragment de détails, ajoutez l'élément <argument> indiqué ci-dessous. Cet argument, appelé selectedProperty, est de type MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Compilez l'application. La navigation génère une erreur, car MarsProperty n'est pas parcelable. L'interface Parcelable permet de sérialiser les objets afin que leurs données puissent être transmises entre les fragments ou les activités. Dans ce cas, pour que les données de l'objet MarsProperty soient transmises au fragment de détails via Safe Args, MarsProperty doit implémenter l'interface Parcelable. La bonne nouvelle est que Kotlin fournit un raccourci simple pour implémenter cette interface.
  2. Ouvrez network/MarsProperty.kt. Ajoutez l'annotation @Parcelize à la définition de la classe.

    Importez kotlinx.android.parcel.Parcelize lorsque vous y êtes invité.

    L'annotation @Parcelize utilise les extensions Kotlin Android pour implémenter automatiquement les méthodes de l'interface Parcelable pour cette classe. Aucune autre action de votre part n'est requise.
@Parcelize
data class MarsProperty (
  1. Modifiez la définition de la classe MarsProperty pour étendre Parcelable.

    Importez android.os.Parcelable lorsque vous y êtes invité.

    La définition de la classe MarsProperty ressemble maintenant à ceci :
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Étape 5 : Connectez les fragments

Vous ne naviguez toujours pas. La navigation proprement dite a lieu dans les fragments. Dans cette étape, vous allez ajouter les derniers éléments pour implémenter la navigation entre les fragments d'aperçu et de détails.

  1. Ouvrez overview/OverviewFragment.kt. Dans onCreateView(), sous les lignes qui initialisent l'adaptateur pour grille de photos, ajoutez les lignes ci-dessous pour observer navigatedToSelectedProperty à partir du ViewModel de la vue d'ensemble.

    Importez androidx.lifecycle.Observer et androidx.navigation.fragment.findNavController lorsque vous y êtes invité.

    L'observateur vérifie si MarsProperty (le it dans le lambda) n'est pas nul. Si c'est le cas, il obtient le contrôleur de navigation à partir du fragment avec findNavController(). Appelez displayPropertyDetailsComplete() pour indiquer au ViewModel de réinitialiser LiveData à l'état nul. Vous ne déclencherez ainsi pas accidentellement une nouvelle navigation lorsque l'application reviendra à OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Ouvrez detail/DetailFragment.kt. Ajoutez cette ligne juste en dessous de l'appel à setLifecycleOwner() dans la méthode onCreateView(). Cette ligne récupère l'objet MarsProperty sélectionné à partir de Safe Args.

    Notez l'utilisation de l'opérateur d'assertion non nul de Kotlin (!!). Si selectedProperty n'est pas là, quelque chose de terrible s'est produit et vous voulez en fait que le code génère un pointeur Null. (Dans le code de production, vous devez gérer cette erreur d'une manière ou d'une autre.)
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Ajoutez ensuite cette ligne pour obtenir un nouveau DetailViewModelFactory. Vous utiliserez DetailViewModelFactory pour obtenir une instance de DetailViewModel. L'application de démarrage inclut une implémentation de DetailViewModelFactory. Il vous suffit donc de l'initialiser.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Enfin, ajoutez cette ligne pour obtenir un DetailViewModel de l'usine et pour connecter toutes les pièces.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Compilez et exécutez l'application, puis appuyez sur une photo de propriété de Mars. Le fragment de détails s'affiche pour les détails de cette propriété. Appuyez sur le bouton Retour pour revenir à la page d'aperçu et remarquez que l'écran de détails est encore un peu vide. Dans la tâche suivante, vous terminerez d'ajouter les données de la propriété à cette page d'informations.

Pour l'instant, la page d'informations n'affiche que la même photo de Mars que celle que vous avez l'habitude de voir sur la page de présentation. La classe MarsProperty possède également un type de propriété (location ou achat) et un prix. L'écran d'informations doit inclure ces deux valeurs. Il serait également utile que les propriétés locatives indiquent que le prix est une valeur mensuelle. Vous utilisez des transformations LiveData dans le modèle de vue pour implémenter ces deux éléments.

  1. Ouvrez res/values/strings.xml. Le code de démarrage inclut des ressources de chaîne, présentées ci-dessous, pour vous aider à créer les chaînes de la vue détaillée. Pour le prix, vous utiliserez la ressource display_price_monthly_rental ou la ressource display_price, selon le type de propriété.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
  1. Ouvrez detail/DetailViewModel.kt. En bas de la classe, ajoutez le code ci-dessous.

    Si vous y êtes invité, importez androidx.lifecycle.Transformations.

    Cette transformation teste si la propriété sélectionnée est une location, en utilisant le même test que dans la première tâche. Si la propriété est une location, la transformation choisit la chaîne appropriée à partir des ressources avec un commutateur Kotlin when {}. Ces deux chaînes doivent comporter un nombre à la fin. Vous devez donc concaténer property.price après.
val displayPropertyPrice = Transformations.map(selectedProperty) {
   app.applicationContext.getString(
           when (it.isRental) {
               true -> R.string.display_price_monthly_rental
               false -> R.string.display_price
           }, it.price)
}
  1. Importez la classe R générée pour accéder aux ressources de chaîne du projet.
import com.example.android.marsrealestate.R
  1. Après la transformation displayPropertyPrice, ajoutez le code ci-dessous. Cette transformation concatène plusieurs ressources de chaîne, selon que le type de propriété est une location ou non.
val displayPropertyType = Transformations.map(selectedProperty) {
   app.applicationContext.getString(R.string.display_type,
           app.applicationContext.getString(
                   when (it.isRental) {
                       true -> R.string.type_rent
                       false -> R.string.type_sale
                   }))
}
  1. Ouvrez res/layout/fragment_detail.xml. Il ne vous reste plus qu'à lier les nouvelles chaînes (que vous avez créées avec les transformations LiveData) à la vue détaillée. Pour ce faire, définissez la valeur du champ de texte pour le texte du type de propriété sur viewModel.displayPropertyType et celle du champ de texte pour le texte de la valeur du prix sur viewModel.displayPropertyPrice.
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. Compilez et exécutez l'application. Toutes les données de propriété s'affichent désormais sur la page d'informations, dans un format agréable.

Projet Android Studio : MarsRealEstateFinal

Expressions de liaison

  • Utilisez des expressions de liaison dans les fichiers de mise en page XML pour effectuer des opérations programmatiques simples, telles que des calculs ou des tests conditionnels, sur les données liées.
  • Pour référencer des classes dans votre fichier de mise en page, utilisez la balise <import> à l'intérieur de la balise <data>.

Options de requête de service Web

  • Les requêtes adressées aux services Web peuvent inclure des paramètres facultatifs.
  • Pour spécifier des paramètres de requête dans la requête, utilisez l'annotation @Query dans Retrofit.

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

À quoi sert la balise <import> dans un fichier de mise en page XML ?

▢ Incluez un fichier de mise en page dans un autre.

▢ Intégrez le code Kotlin dans le fichier de mise en page.

▢ Fournir un accès aux propriétés liées aux données.

▢ Vous permet de référencer des classes et des membres de classe dans des expressions de liaison.

Question 2

Comment ajouter une option de requête à un appel de service Web REST dans Retrofit ?

▢ Ajoutez la requête à la fin de l'URL de requête.

▢ Ajoutez un paramètre à la fonction qui effectue la requête, puis annotez-le avec @Query.

▢ Utilisez la classe Query pour créer une requête.

▢ Utilisez la méthode addQuery() dans le compilateur Retrofit.

Passez à la leçon suivante : 9.1 : Dépôt

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.