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.
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é.
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.
- Accédez à la page du dépôt GitHub fournie pour le projet.
- 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.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.
- 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.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- 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
- Lancez Android Studio.
- Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).
- 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).
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) 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'étatCREATED
. 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'étatCREATED
.onViewCreated()
: cette méthode est appelée après la création de la vue. Avec cette méthode, vous appelez généralementfindViewById()
pour lier des vues spécifiques à des propriétés.onStart()
: le fragment est passé à l'étatSTARTED
.onResume()
: le fragment est passé à l'étatRESUMED
et est désormais actif (peut répondre à l'entrée utilisateur).onPause()
: le fragment est revenu à l'étatSTARTED
. L'utilisateur peut voir l'interface utilisateur.onStop()
: le fragment est revenu à l'étatCREATED
. L'objet est instancié, mais n'est plus affiché à l'écran.onDestroyView()
: appelé juste avant que le fragment passe à l'étatDESTROYED
. La vue a déjà été supprimée de la mémoire, mais l'objet fragment existe toujours.onDestroy()
: le fragment passe à l'étatDESTROYED
.
Le graphique ci-dessous récapitule le cycle de vie du fragment et les transitions entre les états.
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.
- 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 indiquerfragment_letter_list
.
- Pour le second fragment, ajoutez
WordListFragment
dans Nom du fragment. Le champ Nom de mise en page du fragment devrait indiquerfragment_word_list.xml
.
- 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
etWordListFragment
. 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() {
}
- Copiez le contenu de
activity_main.xml
dansfragment_letter_list.xml
et le contenu deactivity_detail.xml
dansfragment_word_list.xml
. Remplaceztools:context
dansfragment_letter_list.xml
par.LetterListFragment
ettools:context
dansfragment_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()
.
- 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 ?
.
- 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.
- Pour afficher le menu d'options, remplacez
onCreate()
. DansonCreate()
, appelezsetHasOptionsMenu()
et transmetteztrue
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
- N'oubliez pas qu'avec les fragments, la mise en page est gonflée dans
onCreateView()
. Pour implémenteronCreateView()
, 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
}
- Sous la propriété
binding
, créez une propriété pour la vue recycleur.
private lateinit var recyclerView: RecyclerView
- Définissez ensuite la valeur de la propriété
recyclerView
dansonViewCreated()
, puis appelezchooseLayout()
comme vous l'avez fait dansMainActivity
. Vous transférerez bientôt la méthodechooseLayout()
versLetterListFragment
. 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.
- Enfin, dans
onDestroyView()
, réinitialisez la propriété_binding
surnull
, car la vue n'existe plus.
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
- Notez également qu'il existe des différences subtiles avec la méthode
onCreateOptionsMenu()
lorsque vous utilisez des fragments. Bien que la classeActivity
ait une propriété globale appeléemenuInflater
, leFragment
n'en a pas. Au lieu de cela, le système de gonflage du menu est transmis à la méthodeonCreateOptionsMenu()
. Notez également que la méthodeonCreateOptionsMenu()
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)
}
- Déplacez le code restant pour
chooseLayout()
,setIcon()
etonOptionsItemSelected()
tel quel à partir deMainActivity
. La seule autre différence est que, contrairement à une activité, un fragment n'est pas un élémentContext
. Vous ne pouvez pas transmettrethis
(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)
}
}
- Enfin, copiez la propriété
isLinearLayoutManager
à partir deMainActivity
. Placez-la juste en dessous de la déclaration de la propriétérecyclerView
.
private var isLinearLayoutManager = true
- Maintenant que toutes les fonctionnalités ont été déplacées vers
LetterListFragment
, il ne reste plus à la classeMainActivity
qu'à gonfler la mise en page pour que le fragment s'affiche dans la vue. Veuillez supprimer toutes les données deMainActivity
, saufonCreate()
. 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 :
- Copiez l'objet associé depuis
DetailActivity
versWordListFragment
. Assurez-vous que la référence àSEARCH_PREFIX
dansWordAdapter
est remplacée par la référenceWordListFragment
. - Ajoutez une variable
_binding
. Cette variable doit pouvoir accepter une valeur nulle, et sa valeur initiale doit êtrenull
. - Ajoutez une variable get-only appelée "binding" comme la variable
_binding
. - Gonflez la mise en page dans
onCreateView()
, en définissant la valeur de_binding
et en renvoyant la vue racine. - 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
dansDetailActivity
) pour obtenir les éléments supplémentaires. - Redéfinissez
_binding
sur "null" dansonDestroyView
. - Supprimez le code restant de
DetailActivity
pour ne conserver que la méthodeonCreate()
.
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.
- Copiez d'abord l'objet associé dans
WordListFragment
.
companion object {
val LETTER = "letter"
val SEARCH_PREFIX = "https://www.google.com/search?q="
}
- Ensuite, dans
LetterAdapter
, dans l'écouteuronClickListener()
dans lequel vous exécutez l'intent, mettez à jour l'appel pour qu'il corresponde àputExtra()
, en remplaçantDetailActivity.LETTER
parWordListFragment.LETTER
.
intent.putExtra(WordListFragment.LETTER, holder.button.text.toString())
- De même, dans
WordAdapter
, mettez à jour l'écouteuronClickListener()
dans lequel vous accédez aux résultats de recherche correspondant au mot, en remplaçantDetailActivity.SEARCH_PREFIX
parWordListFragment.SEARCH_PREFIX
.
val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}")
- De retour dans
WordListFragment
, ajoutez une variable de liaison de typeFragmentWordListBinding?
.
private var _binding: FragmentWordListBinding? = null
- Créez ensuite une variable "get-only" pour pouvoir référencer des vues sans avoir à utiliser
?
.
private val binding get() = _binding!!
- 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 dansonCreateView()
, et non dansonCreate()
.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentWordListBinding.inflate(inflater, container, false)
return binding.root
}
- Ensuite, implémentez
onViewCreated()
. Cela revient presque à configurerrecyclerView
dansonCreate()
dansDetailActivity
. Toutefois, comme les fragments n'ont pas d'accès direct àintent
, vous devez y faire référence avecactivity.intent
. Cette opération doit toutefois être effectuée dansonViewCreated()
, 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)
)
}
- Enfin, réinitialisez la variable
_binding
dansonDestroyView()
.
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
- 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.
- Commencez par supprimer
DetailActivity.kt
.
- Assurez-vous que la case Suppression sécurisée est décochée, puis cliquez sur OK.
- Supprimez ensuite
activity_detail.xml
. Encore une fois, assurez-vous que l'option Safe Delete (Suppression sécurisée) est décochée.
- Enfin, comme
DetailActivity
n'existe plus, supprimez les éléments suivants deAndroidManifest.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émentNavHost
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 dansNavHost
est mise à jour. Vous utiliserez une implémentation intégrée, appeléeNavHostFragment
, dansMainActivity
.NavController
: l'objetNavController
vous permet de contrôler la navigation entre les destinations affichées dansNavHost
. 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éthodenavigate()
deNavController
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é.
Dépendance de la navigation
- Dans le fichier
build.gradle
au niveau du projet, dans buildscript > ext, sousmaterial_version
, définisseznav_version
sur2.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"
}
...
}
- 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 :
- 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"
- Dans le fichier
build.gradle
au niveau de l'application, dans le champplugins
situé en haut, ajoutezandroidx.navigation.safeargs.kotlin
.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
}
- 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.
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
.
- Remplacez le contenu de
FrameLayout
dans le fichier activity_main.xml, à savoir remplacezandroidx.recyclerview.widget.RecyclerView
par un élémentFragmentContainerView
. Attribuez-lui l'IDnav_host_fragment
, puis définissez sa hauteur et sa largeur surmatch_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" />
- Sous l'attribut ID, ajoutez un attribut
name
et définissez-le surandroidx.navigation.fragment.NavHostFragment
. Vous pouvez spécifier un fragment spécifique pour cet attribut, mais le définir surNavHostFragment
permet àFragmentContainerView
de naviguer entre les fragments.
android:name="androidx.navigation.fragment.NavHostFragment"
- 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"
- 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"
- 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'attributapp: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éé.
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
).
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
.
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.
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.
- 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.
- 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.
- Créez une propriété
navController
. Cet élément est marqué commelateinit
, car il sera défini lors de l'étapeonCreate
.
private lateinit var navController: NavController
- Ensuite, après l'appel de
setContentView()
dansonCreate()
, obtenez une référence ànav_host_fragment
(il s'agit de l'ID de la vueFragmentContainerView
) et attribuez-la à la propriéténavController
.
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
- Puis, dans
onCreate()
, appelezsetupActionBarWithNavController()
en transmettantnavController
. Cela garantit que les boutons de la barre d'action, comme l'option de menu deLetterListFragment
, seront visibles.
setupActionBarWithNavController(navController)
- Enfin, implémentez
onSupportNavigateUp()
. En plus de définirdefaultNavHost
surtrue
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é.
- 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
- Remplacez ensuite
onCreate()
(et nononCreateView()
ouonViewCreated()
!), 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.
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.
- Enfin, vous pouvez accéder à
letterId
lorsque vous configurez l'adaptateur de la vue du recycleur. Remplacezactivity?.intent?.extras?.getString(LETTER).toString()
dansonViewCreated()
parletterId
.
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.
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.
- 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>
- Vous pouvez définir le libellé de chaque fragment au niveau du graphique de navigation. Retournez dans
nav_graph.xml
et sélectionnezletterListFragment
dans l'arborescence des composants, puis définissez le libellé sur la chaîneapp_name
dans le volet des attributs :
- Sélectionnez
wordListFragment
et définissez le libellé surword_list_fragment_label
:
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.
- Accédez à la page du dépôt GitHub fournie pour le projet.
- 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.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.
- 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.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- 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
- Lancez Android Studio.
- Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).
- 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).
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) 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 dansonCreateView()
. - 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émentFragmentContainerView
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é.