Les fragments et le composant Navigation

1. Avant de commencer

Dans l'atelier de programmation "Activités et intents", vous avez ajouté des intents dans l'application Words pour passer d'une activité à l'autre. Bien qu'il soit utile de connaître ce modèle de navigation, il ne s'agit que de l'un des nombreux aspects qui entrent en jeu dans la création d'interfaces utilisateur dynamiques pour vos applications. De nombreuses applications Android n'ont pas besoin d'activité distincte pour chaque écran. En fait, de nombreux modèles d'interface utilisateur courants, tels que les onglets, existent au sein d'une même activité et reposent sur ce que l'on appelle des fragments.

64100b59bb487856.png

Un fragment est un élément d'interface utilisateur réutilisable qui peut être intégré dans une ou plusieurs activités. Dans la capture d'écran ci-dessus, appuyer sur un onglet ne déclenche pas d'intent générant l'affichage de l'écran suivant. Au lieu de cela, le passage d'un onglet à un autre remplace simplement le fragment précédent par un autre. Tout cela se passe sans avoir à lancer d'activité supplémentaire.

Vous pouvez même afficher plusieurs fragments à la fois sur le même écran, comme une mise en page maître/détail pour les tablettes. Dans l'exemple ci-dessous, l'interface de navigation de gauche et le contenu de droite peuvent chacun se trouver dans un fragment distinct. Les deux fragments cohabitent simultanément dans la même activité.

b5711344c5795d55.png

Comme vous pouvez le voir, les fragments font partie intégrante de la création d'applications de haute qualité. Dans cet atelier de programmation, vous découvrirez les principes de base des fragments et convertirez l'application Words afin de les utiliser. Vous apprendrez également à vous servir du composant Navigation de Jetpack et à travailler avec un nouveau fichier de ressources appelé graphique de navigation pour naviguer entre les fragments d'une même activité hôte. À la fin de cet atelier de programmation, vous aurez acquis les compétences de base nécessaires pour implémenter des fragments dans votre prochaine application.

Conditions préalables

Avant de suivre cet atelier de programmation, vous devez disposer des compétences ou connaissances suivantes :

  • Vous devez être capable d'ajouter des fichiers XML et Kotlin de ressources à un projet Android Studio.
  • Vous devez connaître le fonctionnement global du cycle de vie d'une activité.
  • Vous devez être capable de remplacer et d'implémenter des méthodes dans une classe existante.
  • Vous devez être capable de créer des instances de classes Kotlin, d'accéder aux propriétés de classe et d'appeler des méthodes.
  • Vous devez posséder des connaissances de base des valeurs pouvant être nulles et non nulles, et devez être capable de gérer les valeurs nulles en toute sécurité.

Points abordés

  • Différence entre le cycle de vie d'un fragment et celui d'une activité
  • Convertir une activité existante en fragment
  • Ajouter des destinations à un graphique de navigation et transmettre des données entre fragments avec le plug-in Safe Args

Objectifs de l'atelier

  • Vous modifierez l'application Words pour qu'elle utilise une seule activité et plusieurs fragments, et naviguerez entre les fragments avec le composant Navigation.

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio
  • Code de solution de l'application Words provenant de l'atelier de programmation "Activités et intents"

2. Code de démarrage

Dans cet atelier de programmation, vous reprendrez là où vous vous étiez arrêté avec l'application Words, à la fin de l'atelier de programmation "Activités et intents". Si vous avez déjà terminé l'atelier de programmation sur les activités et les intents, n'hésitez pas à utiliser votre code comme point de départ. Vous pouvez également télécharger le code depuis GitHub.

Télécharger le code de démarrage pour cet atelier de programmation

Cet atelier de programmation fournit un code de démarrage que vous pouvez étendre avec les fonctionnalités qui y sont enseignées. Le code de démarrage peut contenir du code que vous avez déjà vu dans les ateliers de programmation précédents. Il peut également comporter du code que vous ne connaissez pas et que vous découvrirez dans d'autres ateliers de programmation.

Si vous utilisez le code de démarrage de GitHub, notez que le nom du dossier est android-basics-kotlin-words-app-activities. Sélectionnez ce dossier lorsque vous ouvrirez le projet dans Android Studio.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 8de56cba7583251f.png pour compiler et exécuter l'appli. Assurez-vous que tout fonctionne comme prévu.

3. Fragments et cycle de vie des fragments

Un fragment désigne simplement un élément réutilisable de l'interface utilisateur d'une application. Comme les activités, les fragments ont un cycle de vie et répondent aux entrées utilisateur. Un fragment se trouve toujours dans la hiérarchie de vues d'une activité lorsqu'il est affiché à l'écran. Comme l'accent est mis sur la réutilisation et la modularité, plusieurs fragments peuvent même être hébergés simultanément par une seule activité. Chaque fragment gère son propre cycle de vie.

Cycle de vie des fragments

Tout comme les activités, les fragments peuvent être initialisés et supprimés de la mémoire. Tout au long de leur existence, ils apparaissent, disparaissent et réapparaissent à l'écran. De plus, comme avec les activités, les fragments ont un cycle de vie qui implique plusieurs états. Ils offrent plusieurs méthodes que vous pouvez remplacer pour répondre aux transitions entre elles. Le cycle de vie d'un fragment comporte cinq états, représentés par l'énumération Lifecycle.State.

  • INITIALIZED (INITIALISÉ) : une nouvelle instance du fragment a été instanciée.
  • CREATED (CRÉÉ) : les premières méthodes de cycle de vie du fragment sont appelées. Dans cet état, la vue associée au fragment est également créée.
  • STARTED (DÉMARRÉ) : le fragment est visible à l'écran, mais n'est pas "actif". Il ne peut donc pas répondre à l'entrée utilisateur.
  • RESUMED (RÉACTIVÉ) : le fragment est visible et actif.
  • DESTROYED (DÉTRUIT) : l'instanciation de l'objet fragment a été annulée.

Comme avec les activités, la classe Fragment offre de nombreuses méthodes que vous pouvez remplacer pour répondre aux événements de cycle de vie.

  • onCreate() : le fragment a été instancié et présente l'état CREATED. Cependant, la vue correspondante n'a pas encore été créée.
  • onCreateView() : cette méthode vous permet de gonfler la mise en page. Le fragment est passé à l'état CREATED.
  • onViewCreated() : cette méthode est appelée après la création de la vue. Avec cette méthode, vous appelez généralement findViewById() pour lier des vues spécifiques à des propriétés.
  • onStart() : le fragment est passé à l'état STARTED.
  • onResume() : le fragment est passé à l'état RESUMED et est désormais actif (peut répondre à l'entrée utilisateur).
  • onPause() : le fragment est revenu à l'état STARTED. L'utilisateur peut voir l'interface utilisateur.
  • onStop() : le fragment est revenu à l'état CREATED. L'objet est instancié, mais n'est plus affiché à l'écran.
  • onDestroyView() : appelé juste avant que le fragment passe à l'état DESTROYED. La vue a déjà été supprimée de la mémoire, mais l'objet fragment existe toujours.
  • onDestroy() : le fragment passe à l'état DESTROYED.

Le graphique ci-dessous récapitule le cycle de vie du fragment et les transitions entre les états.

8dc30a4c12ab71b.png

Les états du cycle de vie et les méthodes de rappel sont assez semblables à ceux utilisés pour les activités. Cependant, gardez à l'esprit la différence avec la méthode onCreate(). Avec les activités, cette méthode vous permet d'améliorer la mise en page et de lier des vues. Cependant, dans le cycle de vie d'un fragment, onCreate() est appelé avant la création de la vue. Vous ne pouvez donc pas gonfler la mise en page ici. Au lieu de cela, vous devez procéder dans onCreateView(). Ensuite, une fois la vue créée, la méthode onViewCreated() est appelée. Elle vous permet de lier des propriétés à des vues spécifiques.

Bien que toutes ces informations aient pu vous sembler un peu trop théoriques, vous connaissez maintenant les principes de fonctionnement des fragments, ainsi que leur similitude et leur différence avec les activités. Dans la suite de cet atelier de programmation, vous mettrez en pratique ces connaissances. Vous commencerez par migrer l'application Words sur laquelle vous avez travaillé précédemment afin d'utiliser une mise en page basée sur des fragments. Puis, vous implémenterez la navigation entre les fragments au sein d'une même activité.

4. Créer les fichiers de fragment et de mise en page

Comme pour les activités, chaque fragment que vous ajoutez comprend deux fichiers : un fichier XML pour la mise en page et une classe Kotlin pour afficher les données et gérer les interactions des utilisateurs. Vous ajouterez un fragment à la fois pour la liste de lettres et la liste de mots.

  1. Veillez à ce que l'option appli soit sélectionnée dans le navigateur du projet, puis ajoutez les fragments suivants via Fichier > Nouveau > Fragment > Fragment (Vide). Une classe et un fichier mise en page devraient être générés pour chaque fragment.
  • Pour le premier fragment, ajoutez LetterListFragment dans Nom du fragment. Le champ Nom de mise en page du fragment devrait indiquer fragment_letter_list.

4a1729f01d62e65e.png

  • Pour le second fragment, ajoutez WordListFragment dans Nom du fragment. Le champ Nom de mise en page du fragment devrait indiquer fragment_word_list.xml.

5b86ff3a94833b5a.png

  1. Les classes Kotlin générées pour les deux fragments contiennent une grande quantité de code récurrent couramment utilisé lors de l'implémentation des fragments. Toutefois, si vous vous familiarisez avec les fragments pour la première fois, supprimez des deux fichiers l'ensemble des éléments, à l'exception de la déclaration de classe pour LetterListFragment et WordListFragment. Nous vous expliquerons comment implémenter les fragments à partir de zéro afin que vous compreniez bien comment fonctionne l'ensemble du code. Une fois le code récurrent supprimé, les fichiers Kotlin devraient se présenter comme suit.

LetterListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class LetterListFragment : Fragment() {

}

WordListFragment.kt

package com.example.wordsapp

import androidx.fragment.app.Fragment

class WordListFragment : Fragment() {

}
  1. Copiez le contenu de activity_main.xml dans fragment_letter_list.xml et le contenu de activity_detail.xml dans fragment_word_list.xml. Remplacez tools:context dans fragment_letter_list.xml par .LetterListFragment et tools:context dans fragment_word_list.xml par .WordListFragment.

Une fois les modifications effectuées, les fichiers de mise en page de fragment devraient se présenter comme suit.

fragment_letter_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".LetterListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp" />

</FrameLayout>

fragment_word_list.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".WordListFragment">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:clipToPadding="false"
       android:padding="16dp"
       tools:listitem="@layout/item_view" />

</FrameLayout>

5. Implémenter LetterListFragment

Comme pour les activités, vous devez gonfler la mise en page et lier des vues individuelles. Il existe quelques différences mineures lorsque vous utilisez le cycle de vie d'un fragment. Nous vous guiderons tout au long du processus de configuration de LetterListFragment. Vous pourrez ensuite suivre ce processus pour WordListFragment.

Pour lier les vues dans LetterListFragment, vous devez d'abord obtenir une référence pouvant être nulle pour FragmentLetterListBinding. Les classes de ce type sont générées par Android Studio pour chaque fichier de mise en page, lorsque la propriété viewBinding est activée dans la section buildFeatures du fichier build.gradle. Il vous suffit d'attribuer des propriétés à la classe de fragment pour chaque vue dans FragmentLetterListBinding.

Le type doit correspondre à FragmentLetterListBinding?, et la valeur initiale à null. Pourquoi la rendre nulle ? Parce que vous ne pouvez pas gonfler la mise en page avant d'appeler onCreateView(). Il existe un délai entre la création de l'instance LetterListFragment (lorsque son cycle de vie commence avec onCreate()) et le moment où cette propriété est réellement utilisable. N'oubliez pas que les vues d'un fragment peuvent être créées et détruites plusieurs fois au cours de son cycle de vie. C'est pourquoi vous devez également réinitialiser la valeur dans une autre méthode de cycle de vie, onDestroyView().

  1. Dans LetterListFragment.kt, commencez par obtenir une référence à FragmentLetterListBinding, puis nommez-la _binding.
private var _binding: FragmentLetterListBinding? = null

Étant donné qu'elle peut accepter la valeur nulle, chaque fois que vous accédez à une propriété _binding (par exemple, _binding?.someView), vous devez inclure ? pour la sécurité null. Toutefois, cela ne signifie pas que vous devez encombrer votre code de points d'interrogation à cause d'une seule valeur nulle. Si vous êtes certain qu'une valeur n'est pas nulle lorsque vous y accédez, vous pouvez ajouter !! à son nom. Vous pourrez ensuite y accéder comme n'importe quelle autre propriété, sans l'opérateur ?.

  1. Créez une propriété appelée "binding" (sans le trait de soulignement) et définissez-la sur _binding!!.
private val binding get() = _binding!!

Ici, get() signifie que cette propriété est de type "get-only". En d'autres termes, vous pouvez obtenir la valeur, mais une fois qu'elle aura été attribuée (comme c'est le cas ici), vous ne pourrez pas l'attribuer à autre chose.

  1. Pour afficher le menu d'options, remplacez onCreate(). Dans onCreate(), appelez setHasOptionsMenu() et transmettez true.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setHasOptionsMenu(true)
}
  1. N'oubliez pas qu'avec les fragments, la mise en page est gonflée dans onCreateView(). Pour implémenter onCreateView(), gonflez la vue, définissez la valeur _binding et renvoyez la vue racine.
override fun onCreateView(
   inflater: LayoutInflater, container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentLetterListBinding.inflate(inflater, container, false)
   val view = binding.root
   return view
}
  1. Sous la propriété binding, créez une propriété pour la vue recycleur.
private lateinit var recyclerView: RecyclerView
  1. Définissez ensuite la valeur de la propriété recyclerView dans onViewCreated(), puis appelez chooseLayout() comme vous l'avez fait dans MainActivity. Vous transférerez bientôt la méthode chooseLayout() vers LetterListFragment. Par conséquent, ne vous inquiétez pas s'il y a une erreur.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   recyclerView = binding.recyclerView
   chooseLayout()
}

Notez que la classe de liaison a déjà créé une propriété pour recyclerView et que vous n'avez pas besoin d'appeler findViewById() pour chaque vue.

  1. Enfin, dans onDestroyView(), réinitialisez la propriété _binding sur null, car la vue n'existe plus.
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. Notez également qu'il existe des différences subtiles avec la méthode onCreateOptionsMenu() lorsque vous utilisez des fragments. Bien que la classe Activity ait une propriété globale appelée menuInflater, le Fragment n'en a pas. Au lieu de cela, le système de gonflage du menu est transmis à la méthode onCreateOptionsMenu(). Notez également que la méthode onCreateOptionsMenu() utilisée avec les fragments ne nécessite pas d'instruction return. Implémentez la méthode comme indiqué ci-dessous :
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
   inflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu.findItem(R.id.action_switch_layout)
   setIcon(layoutButton)
}
  1. Déplacez le code restant pour chooseLayout(), setIcon() et onOptionsItemSelected() tel quel à partir de MainActivity. La seule autre différence est que, contrairement à une activité, un fragment n'est pas un élément Context. Vous ne pouvez pas transmettre this (faisant référence à l'objet fragment) comme contexte du gestionnaire de mises en page. Toutefois, les fragments fournissent une propriété context que vous pouvez utiliser à la place. Le reste du code est identique à MainActivity.
private fun chooseLayout() {
   when (isLinearLayoutManager) {
       true -> {
           recyclerView.layoutManager = LinearLayoutManager(context)
           recyclerView.adapter = LetterAdapter()
       }
       false -> {
           recyclerView.layoutManager = GridLayoutManager(context, 4)
           recyclerView.adapter = LetterAdapter()
       }
   }
}

private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_linear_layout)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           isLinearLayoutManager = !isLinearLayoutManager
           chooseLayout()
           setIcon(item)

           return true
       }

       else -> super.onOptionsItemSelected(item)
   }
}
  1. Enfin, copiez la propriété isLinearLayoutManager à partir de MainActivity. Placez-la juste en dessous de la déclaration de la propriété recyclerView.
private var isLinearLayoutManager = true
  1. Maintenant que toutes les fonctionnalités ont été déplacées vers LetterListFragment, il ne reste plus à la classe MainActivity qu'à gonfler la mise en page pour que le fragment s'affiche dans la vue. Veuillez supprimer toutes les données de MainActivity, sauf onCreate(). Une fois les modifications effectuées, MainActivity ne doit contenir que les éléments suivants.
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityMainBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

À vous de jouer !

Voilà, vous avez transféré MainActivity vers LettersListFragment. La migration de DetailActivity est presque identique. Pour transférer le code vers WordListFragment, procédez comme suit :

  1. Copiez l'objet associé depuis DetailActivity vers WordListFragment. Assurez-vous que la référence à SEARCH_PREFIX dans WordAdapter est remplacée par la référence WordListFragment.
  2. Ajoutez une variable _binding. Cette variable doit pouvoir accepter une valeur nulle, et sa valeur initiale doit être null.
  3. Ajoutez une variable get-only appelée "binding" comme la variable _binding.
  4. Gonflez la mise en page dans onCreateView(), en définissant la valeur de _binding et en renvoyant la vue racine.
  5. Effectuez la configuration restante dans onViewCreated() : obtenez une référence à la vue recycleur, définissez le gestionnaire et l'adaptateur de mise en page, puis ajoutez la décoration de l'élément. Vous devez obtenir la lettre à partir de l'intent. Les fragments ne possèdent pas de propriété intent et ne devraient normalement pas accéder à l'intent de l'activité parent. Pour le moment, faites référence à activity.intent (plutôt qu'à intent dans DetailActivity) pour obtenir les éléments supplémentaires.
  6. Redéfinissez _binding sur "null" dans onDestroyView.
  7. Supprimez le code restant de DetailActivity pour ne conserver que la méthode onCreate().

Essayez de suivre cette procédure par vous-même avant de continuer. Un tutoriel détaillé est disponible à l'étape suivante.

6. Convertir DetailActivity en WordListFragment

Nous espérons que vous avez trouvé la migration de DetailActivity vers WordListFragment intéressante. Cette procédure est presque identique à la migration de MainActivity vers LetterListFragment. Si vous avez été bloqué à un moment donné, les étapes sont récapitulées ci-dessous.

  1. Copiez d'abord l'objet associé dans WordListFragment.
companion object {
   val LETTER = "letter"
   val SEARCH_PREFIX = "https://www.google.com/search?q="
}
  1. Ensuite, dans LetterAdapter, dans l'écouteur onClickListener() dans lequel vous exécutez l'intent, mettez à jour l'appel pour qu'il corresponde à putExtra(), en remplaçant DetailActivity.LETTER par WordListFragment.LETTER.
intent.putExtra(WordListFragment.LETTER, holder.button.text.toString())
  1. De même, dans WordAdapter, mettez à jour l'écouteur onClickListener() dans lequel vous accédez aux résultats de recherche correspondant au mot, en remplaçant DetailActivity.SEARCH_PREFIX par WordListFragment.SEARCH_PREFIX.
val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}")
  1. De retour dans WordListFragment, ajoutez une variable de liaison de type FragmentWordListBinding?.
private var _binding: FragmentWordListBinding? = null
  1. Créez ensuite une variable "get-only" pour pouvoir référencer des vues sans avoir à utiliser ?.
private val binding get() = _binding!!
  1. Gonflez la mise en page, en attribuant la variable _binding et en renvoyant la vue racine. N'oubliez pas que, pour les fragments, cette opération doit être effectuée dans onCreateView(), et non dans onCreate().
override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentWordListBinding.inflate(inflater, container, false)
   return binding.root
}
  1. Ensuite, implémentez onViewCreated(). Cela revient presque à configurer recyclerView dans onCreate() dans DetailActivity. Toutefois, comme les fragments n'ont pas d'accès direct à intent, vous devez y faire référence avec activity.intent. Cette opération doit toutefois être effectuée dans onViewCreated(), car il n'existe aucune garantie que l'activité existe plus tôt dans le cycle de vie.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   val recyclerView = binding.recyclerView
   recyclerView.layoutManager = LinearLayoutManager(requireContext())
   recyclerView.adapter = WordAdapter(activity?.intent?.extras?.getString(LETTER).toString(), requireContext())

   recyclerView.addItemDecoration(
       DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
   )
}
  1. Enfin, réinitialisez la variable _binding dans onDestroyView().
override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}
  1. Maintenant que toutes ces fonctionnalités ont été transférées vers WordListFragment, vous pouvez supprimer le code de DetailActivity. Il ne vous reste plus qu'à utiliser la méthode onCreate().
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   val binding = ActivityDetailBinding.inflate(layoutInflater)
   setContentView(binding.root)
}

Supprimer DetailActivity

Maintenant que vous avez migré la fonctionnalité de DetailActivity vers WordListFragment, vous n'avez plus besoin de DetailActivity. Vous pouvez supprimer DetailActivity.kt et activity_detail.xml, et apporter une légère modification au fichier manifeste.

  1. Commencez par supprimer DetailActivity.kt.

2b13b08ac9442ae5.png

  1. Assurez-vous que la case Suppression sécurisée est décochée, puis cliquez sur OK.

239f048d945ab1f9.png

  1. Supprimez ensuite activity_detail.xml. Encore une fois, assurez-vous que l'option Safe Delete (Suppression sécurisée) est décochée.

774c8b152c5bff6b.png

  1. Enfin, comme DetailActivity n'existe plus, supprimez les éléments suivants de AndroidManifest.xml.
<activity
   android:name=".DetailActivity"
   android:parentActivityName=".MainActivity" />

Après avoir supprimé l'activité des détails, il vous reste deux fragments (LetterListFragment et WordListFragment) et une activité unique (MainActivity). Dans la section suivante, vous découvrirez le composant Navigation de Jetpack et modifierez activity_main.xml pour permettre son affichage et la navigation entre les fragments, au lieu d'héberger une mise en page statique.

7. Composant Navigation de Jetpack

Android Jetpack offre le composant Navigation pour vous aider à gérer n'importe quelle mise en œuvre de navigation, simple ou complexe, dans votre application. Le composant Navigation comporte trois parties clés qui permettent d'implémenter la navigation dans l'application Words.

  • Graphique de navigation : il s'agit d'un fichier XML qui offre une représentation visuelle de la navigation dans votre application. Ce fichier est constitué de destinations, qui correspondent à des activités et des fragments individuels, ainsi que d'actions qui peuvent être utilisées dans le code pour naviguer d'une destination à une autre. Tout comme avec les fichiers de mise en page, Android Studio fournit un éditeur visuel permettant d'ajouter des destinations et des actions au graphique de navigation.
  • NavHost : un élément NavHost permet d'afficher les destinations à partir d'un graphique de navigation au sein d'une activité. Lorsque vous naviguez entre des fragments, la destination affichée dans NavHost est mise à jour. Vous utiliserez une implémentation intégrée, appelée NavHostFragment, dans MainActivity.
  • NavController : l'objet NavController vous permet de contrôler la navigation entre les destinations affichées dans NavHost. Lorsque vous avez utilisé des intents, vous avez appelé la fonction startActivity pour accéder à un nouvel écran. Avec le composant Navigation, vous pouvez appeler la méthode navigate() de NavController pour permuter le fragment affiché. NavController vous aide également à gérer des tâches courantes, comme répondre au bouton "up" (haut) du système pour revenir au fragment précédemment affiché.
  1. Dans le fichier build.gradle au niveau du projet, dans buildscript > ext, sous material_version, définissez nav_version sur 2.5.2.
buildscript {
    ext {
        appcompat_version = "1.5.1"
        constraintlayout_version = "2.1.4"
        core_ktx_version = "1.9.0"
        kotlin_version = "1.7.10"
        material_version = "1.7.0-alpha2"
        nav_version = "2.5.2"
    }

    ...
}
  1. Dans le fichier build.gradle au niveau de l'application, ajoutez le code suivant au groupe de dépendances.= :
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Plug-in Safe Args

Lorsque vous avez intégré pour la première fois la navigation dans l'application Words, vous avez utilisé un intent explicite entre les deux activités. Pour transmettre des données entre les deux activités, vous avez appelé la méthode putExtra() en transmettant la lettre sélectionnée.

Avant d'implémenter le composant Navigation dans l'application Words, vous allez également ajouter un composant nommé Safe Args. Il s'agit d'un plug-in Gradle qui vous aide à sécuriser les types lors de la transmission de données entre fragments.

Pour intégrer SafeArgs à votre projet, procédez comme suit :

  1. Dans le fichier build.gradle de premier niveau, dans buildscript > dépendances, ajoutez le chemin de classe suivant.
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
  1. Dans le fichier build.gradle au niveau de l'application, dans le champ plugins situé en haut, ajoutez androidx.navigation.safeargs.kotlin.
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs.kotlin'
}
  1. Une fois que vous avez modifié les fichiers Gradle, une bannière jaune en haut de l'écran peut vous demander de synchroniser le projet. Cliquez sur Synchroniser maintenant, puis patientez une minute ou deux, le temps que Gradle mette à jour les dépendances du projet afin de refléter vos modifications.

854d44a6f7c4c080.png

Une fois la synchronisation terminée, vous pouvez passer à l'étape suivante, au cours de laquelle vous ajouterez un graphique de navigation.

8. Fonctionnement du graphique de navigation

Maintenant que vous connaissez les principes de base des fragments et de leur cycle de vie, il est temps de passer à la vitesse supérieure. L'étape suivante consiste à intégrer le composant Navigation. Le composant Navigation fait simplement référence à un ensemble d'outils permettant d'implémenter la navigation, en particulier entre les fragments. Vous utiliserez un nouvel éditeur visuel pour faciliter la mise en œuvre de la navigation entre les fragments : le graphique de navigation (ou NavGraph).

Qu'est-ce qu'un graphique de navigation ?

Le graphique de navigation (ou NavGraph) est un mappage virtuel de la navigation dans votre application. Chaque écran ou, dans votre cas, chaque fragment, deviendra une "destination" à laquelle il sera possible d'accéder. Un NavGraph peut être représenté par un fichier XML qui montre comment les destinations sont liées.

En arrière-plan, cela crée en fait une instance de la classe NavGraph. Toutefois, FragmentContainerView peut présenter à l'utilisateur les destinations du graphique de navigation. Il vous suffit de créer un fichier XML et de spécifier les destinations possibles. Vous pourrez ensuite utiliser le code généré pour naviguer entre les fragments.

Utiliser FragmentContainerView dans MainActivity

Étant donné que vos mises en page sont désormais incluses dans fragment_letter_list.xml et fragment_word_list.xml, votre fichier activity_main.xml n'a plus besoin de comprendre la mise en page du premier écran dans votre application. À la place, vous réutiliserez MainActivity pour qu'il contienne un élément FragmentContainerView qui servira d'hôte de navigation pour vos fragments. À partir de maintenant, toute la navigation dans l'application se passera dans FragmentContainerView.

  1. Remplacez le contenu de FrameLayout dans le fichier activity_main.xml, à savoir remplacez androidx.recyclerview.widget.RecyclerView par un élément FragmentContainerView. Attribuez-lui l'ID nav_host_fragment, puis définissez sa hauteur et sa largeur sur match_parent pour remplir toute la mise en page du cadre.

Remplacez cet extrait :

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        ...
        android:padding="16dp" />

Par cet extrait :

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/nav_host_fragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Sous l'attribut ID, ajoutez un attribut name et définissez-le sur androidx.navigation.fragment.NavHostFragment. Vous pouvez spécifier un fragment spécifique pour cet attribut, mais le définir sur NavHostFragment permet à FragmentContainerView de naviguer entre les fragments.
android:name="androidx.navigation.fragment.NavHostFragment"
  1. Sous les attributs "layout_height" et "layout_width", ajoutez un attribut appelé app:defaultNavHost et définissez-le sur "true". Cette action permet au conteneur de fragment d'interagir avec la hiérarchie de navigation. Par exemple, si vous appuyez sur le bouton "Retour" du système, le conteneur reviendra au fragment précédemment affiché, comme lorsqu'une nouvelle activité est présentée.
app:defaultNavHost="true"
  1. Ajoutez un attribut appelé app:navGraph et définissez-le sur "@navigation/nav_graph". Ce fichier pointe vers un fichier XML qui définit la façon dont les fragments de l'application peuvent accéder les uns aux autres. Pour le moment, Android Studio affiche une erreur de symbole non résolu. Vous résoudrez ce problème dans la prochaine tâche.
app:navGraph="@navigation/nav_graph"
  1. Enfin, comme vous avez ajouté deux attributs avec l'espace de noms de l'application, veillez à ajouter l'attribut xmlns:app à FrameLayout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

Toutes les modifications nécessaires ont été apportées dans le fichier activity_main.xml. Vous allez maintenant créer le fichier nav_graph.

Configurer le graphique de navigation

Ajoutez un fichier de graphique de navigation via Fichier > Nouveau > Fichier de ressource Android, puis remplissez les champs comme suit.

  • Nom de fichier : nav_graph.xml.. Il est identique au nom que vous avez défini pour l'attribut app:navGraph.
  • Type de ressource : Navigation. Le nom du répertoire devrait alors automatiquement être remplacé par "navigation". Un dossier de ressources nommé "navigation" est créé.

6812c83aa1e9cea6.png

Lorsque vous créez le fichier XML, un nouvel éditeur visuel vous est présenté. Étant donné que vous avez déjà référencé nav_graph dans la propriété navGraph de FragmentContainerView, pour ajouter une destination, cliquez sur le nouveau bouton dans la partie supérieure gauche de l'écran et créez une destination pour chaque fragment (une destination pour fragment_letter_list et une autre pour fragment_word_list).

dc2b53782de5e143.gif

Une fois ajoutés, ces fragments devraient apparaître dans le graphique de navigation au milieu de l'écran. Vous pouvez également sélectionner une destination spécifique à l'aide de l'arborescence de composants qui s'affiche sur la gauche.

Créer une action de navigation

Pour créer une action de navigation entre les destinations letterListFragment et wordListFragment, pointez sur la destination letterListFragment et faites-la glisser depuis le cercle qui s'affiche à droite vers la destination wordListFragment.

980cb34d800c7155.gif

Vous devriez désormais voir une flèche représentant l'action entre les deux destinations. Cliquez sur cette flèche. Dans le volet des attributs, vous pouvez voir que cette action porte un nom (action_letterListFragment_to_wordListFragment) qui peut être référencé dans le code.

Spécifier des arguments pour WordListFragment

Lors de la navigation entre les activités à l'aide d'un intent, vous avez spécifié un "extra" afin que la lettre sélectionnée puisse être transmise à wordListFragment. La navigation permet également de transmettre des paramètres entre les destinations, et le bouton Plus permet de le faire sans risque.

Sélectionnez la destination wordListFragment, puis, dans le volet des attributs, sous Arguments, cliquez sur le bouton Plus pour créer un argument.

L'argument doit être appelé letter, tandis que le type doit correspondre à String. C'est là que le plug-in Safe Args que vous avez ajouté précédemment entre en jeu. La spécification de cet argument en tant que chaîne garantit qu'une valeur String est attendue lorsque votre action de navigation est effectuée dans le code.

f1541e01d3462f3e.png

Définir la destination de départ

Bien que le NavGraph connaisse toutes les destinations requises, comment FragmentContainerView détermine-t-il quel fragment afficher en premier ? Dans le NavGraph, vous devez définir la liste de lettres comme destination de départ.

Pour ce faire, sélectionnez letterListFragment et cliquez sur le bouton Attribuer une destination de départ.

3fdb226894152fb0.png

  1. Pour l'instant, c'est tout ce que vous avez à faire avec l'éditeur NavGraph. À ce stade, vous pouvez créer le projet. Dans Android Studio, sélectionnez Compiler > Recompiler le projet dans la barre de menu. Un code basé sur votre graphique de navigation sera alors généré, ce qui vous permettra d'utiliser l'action de navigation que vous venez de créer.

Effectuer l'action de navigation

Ouvrez LetterAdapter.kt pour effectuer l'action de navigation. Seulement deux étapes sont nécessaires.

  1. Supprimez le contenu de l'élément setOnClickListener() du bouton. Au lieu de cela, vous devez récupérer l'action de navigation que vous venez de créer. Ajoutez les éléments suivants à setOnClickListener().
val action = LetterListFragmentDirections.actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString())

Vous ne reconnaissez probablement pas certains de ces noms de classe et de fonction, car ils ont été générés automatiquement après la création du projet. C'est là que le plug-in Safe Args que vous avez ajouté à la première étape entre en jeu. Les actions créées dans le NavGraph sont converties en code que vous pouvez utiliser. Toutefois, les noms devraient être assez intuitifs. LetterListFragmentDirections vous permet de faire référence à tous les chemins de navigation possibles à compter de letterListFragment.

La fonction actionLetterListFragmentToWordListFragment()

est l'action spécifique permettant d'accéder à wordListFragment.

Une fois que vous avez la référence à votre action de navigation, il vous suffit d'obtenir la référence à NavController (objet qui permet d'effectuer des actions de navigation) et d'appeler navigate() pour transmettre l'action.

holder.view.findNavController().navigate(action)

Configurer MainActivity

La dernière étape de configuration se trouve dans MainActivity. Quelques changements sont nécessaires dans MainActivity pour que tout fonctionne bien.

  1. Créez une propriété navController. Cet élément est marqué comme lateinit, car il sera défini lors de l'étape onCreate.
private lateinit var navController: NavController
  1. Ensuite, après l'appel de setContentView() dans onCreate(), obtenez une référence à nav_host_fragment (il s'agit de l'ID de la vue FragmentContainerView) et attribuez-la à la propriété navController.
val navHostFragment = supportFragmentManager
    .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
  1. Puis, dans onCreate(), appelez setupActionBarWithNavController() en transmettant navController. Cela garantit que les boutons de la barre d'action, comme l'option de menu de LetterListFragment, seront visibles.
setupActionBarWithNavController(navController)
  1. Enfin, implémentez onSupportNavigateUp(). En plus de définir defaultNavHost sur true dans le fichier XML, cette méthode vous permet de gérer le bouton up (haut). Toutefois, c'est votre activité qui doit fournir l'implémentation.
override fun onSupportNavigateUp(): Boolean {
   return navController.navigateUp() || super.onSupportNavigateUp()
}

À ce stade, tous les composants sont en place pour que la navigation fonctionne avec les fragments. Toutefois, maintenant que la navigation est effectuée à l'aide de fragments au lieu de l'intent, l'intent "extra" pour la lettre que vous utilisez dans WordListFragment ne fonctionnera plus. À l'étape suivante, vous mettrez à jour WordListFragment pour obtenir l'argument letter.

9. Obtenir des arguments dans WordListFragment

Vous avez précédemment référencé activity?.intent dans WordListFragment pour accéder à l'élément supplémentaire letter. Bien que cela fonctionne, cette pratique est déconseillée, car les fragments peuvent être intégrés dans d'autres mises en page. Dans une application plus grande, il est beaucoup plus difficile de supposer à quelle activité le fragment appartient. De plus, lorsque la navigation est effectuée à l'aide de nav_graph et que des arguments sécurisés sont utilisés, il n'y a pas d'intents, ce qui signifie que l'accès à des extras d'intent ne fonctionnera tout simplement pas.

Heureusement, l'accès aux arguments sécurisés est assez simple, et vous n'avez pas non plus à attendre que onViewCreated() soit appelé.

  1. Dans WordListFragment, créez une propriété letterId. Vous pouvez la marquer comme "lateinit" afin de ne pas avoir à la rendre nulle.
private lateinit var letterId: String
  1. Remplacez ensuite onCreate() (et non onCreateView() ou onViewCreated() !), puis ajoutez ce qui suit :
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    arguments?.let {
        letterId = it.getString(LETTER).toString()
    }
}

Étant donné qu'il est possible que les arguments soient facultatifs, vous pouvez appeler let() et transmettre une expression lambda. Ce code s'exécute en supposant que l'élément arguments n'est pas nul, en transmettant les arguments non nuls pour le paramètre it. Toutefois, si l'élément arguments est null, l'expression lambda ne s'exécutera pas.

96a6a3253cea35b0.png

Bien que cela ne fasse pas partie du code réel, Android Studio fournit un indice utile pour attirer votre attention sur le paramètre it.

Qu'est-ce qu'un Bundle exactement ? Il s'agit d'une paire clé/valeur utilisée pour transmettre des données entre les classes, telles que les activités et les fragments. Vous avez d'ailleurs déjà utilisé un bundle pour appeler intent?.extras?.getString() lors de l'exécution d'un intent dans la première version de cette application. La procédure à suivre pour obtenir la chaîne à partir d'arguments est la même.

  1. Enfin, vous pouvez accéder à letterId lorsque vous configurez l'adaptateur de la vue du recycleur. Remplacez activity?.intent?.extras?.getString(LETTER).toString() dans onViewCreated() par letterId.
recyclerView.adapter = WordAdapter(letterId, requireContext())

Bravo ! Prenez quelques instants pour exécuter votre application. Elle peut désormais naviguer entre deux écrans, sans intent, et ce en une seule activité.

10. Mettre à jour les libellés de fragment

Vous avez correctement converti les deux écrans afin qu'ils utilisent des fragments. Avant que des modifications y soient apportées, la barre d'application de chaque fragment comportait un titre descriptif pour chaque activité qui s'y trouvait. Toutefois, une fois la conversion effectuée pour utiliser des fragments, ce titre ne figure pas dans l'activité des détails.

c385595994ba91b5.png

Les fragments possèdent une propriété appelée "label", dans laquelle vous pouvez définir le titre que l'activité parent utilisera dans la barre d'application.

  1. Dans strings.xml, après le nom de l'application, ajoutez la constante suivante.
<string name="word_list_fragment_label">Words That Start With {letter}</string>
  1. Vous pouvez définir le libellé de chaque fragment au niveau du graphique de navigation. Retournez dans nav_graph.xml et sélectionnez letterListFragment dans l'arborescence des composants, puis définissez le libellé sur la chaîne app_name dans le volet des attributs :

a5ffe7a27aa03750.png

  1. Sélectionnez wordListFragment et définissez le libellé sur word_list_fragment_label :

29c206f03a97557b.png

Bravo ! Exécutez votre application une fois de plus. Vous devriez voir la même chose qu'au début de l'atelier de programmation, mais maintenant, toute votre navigation est hébergée dans une activité unique avec un fragment distinct pour chaque écran.

11. Code de solution

Le code de solution de cet atelier de programmation figure dans le projet ci-dessous.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 8de56cba7583251f.png pour compiler et exécuter l'appli. Assurez-vous que tout fonctionne comme prévu.

12. Résumé

  • Les fragments sont des éléments d'interface utilisateur réutilisables qui peuvent être intégrés dans des activités.
  • Le cycle de vie d'un fragment diffère du cycle de vie d'une activité, car la configuration de la vue s'effectue dans onViewCreated() et non dans onCreateView().
  • Un élément FragmentContainerView permet d'intégrer des fragments dans d'autres activités et de gérer la navigation entre eux.

Fonctionnement du composant de navigation

  • La définition de l'attribut navGraph d'un élément FragmentContainerView vous permet de naviguer entre les fragments d'une activité.
  • L'éditeur NavGraph vous permet d'ajouter des actions de navigation et de spécifier des arguments entre différentes destinations.
  • Bien que la navigation à l'aide d'intents nécessite la transmission d'éléments supplémentaires, le composant Navigation utilise SafeArgs pour générer automatiquement des classes et des méthodes pour vos actions de navigation, ce qui garantit la sécurité des types avec des arguments.

Cas d'utilisation des fragments

  • Grâce au composant Navigation, de nombreuses applications peuvent gérer l'ensemble de leur mise en page au sein d'une même activité, la navigation se faisant entre les fragments.
  • Les fragments permettent des modèles de mise en page courants, tels que les mises en page maître/détail sur les tablettes ou plusieurs onglets dans la même activité.

13. En savoir plus