Il y a 4 ans -
Temps de lecture 13 minutes
Android : Navigation Architecture Component
Pour naviguer entre écrans en Android, on retrouve classiquement les problèmes suivants :
- gestion des transactions entre Fragments ;
- passage et récupération d’arguments entre Fragments ;
- comportement des boutons Up et Back ;
- implémentation d’un Deep linking cohérent ;
- tester un Fragment en isolation.
En réponse à ces problématiques, Google a annoncé l’arrivée du Navigation Architecture Component au sein d’Android Jetpack lors de la Google I/O 2018.
Ce composant met à disposition du code et des outils dont le but est de simplifier l’implémentation de la navigation dans une application Android.
À ce jour, le Navigation Architecture Component n’est disponible qu’en version alpha.
Dans cet article, nous allons découvrir ensemble les différents aspects du Navigation Architecture Component :
- configuration ;
- navigation vers une destination ;
- animations de transition ;
- passage d’arguments ;
- lien avec des vues ;
- deep links ;
- destinations personnalisées ;
- tests.
Configuration
Pour pouvoir utiliser le Navigation Architecture Component, on a besoin de configurer le projet.
Activer le Navigation Editor
Le Navigation Editor est un outil du Navigation Architecture Component permettant d’éditer et de visualiser la navigation sous forme d’un graphe.
Pour pouvoir l’utiliser dans Android Studio, allez dans Preferences → Experimental et cochez Enable Navigation Editor.
Importer les dépendances Gradle
Ajoutez les dépendances suivantes dans le fichier build.gradle de votre module :
[groovy]dependencies {
// …
implementation ‘android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha08’
implementation ‘android.arch.navigation:navigation-ui-ktx:1.0.0-alpha08’
}[/groovy]
Dépendance | Usage |
---|---|
android.arch.navigation:navigation-fragment-ktx | Permet d’utiliser des fragments pour la navigation. |
android.arch.navigation:navigation-ui-ktx | Permet de lier la navigation avec différentes vues. |
Créer un Navigation Graph
Navigation Architecture Component se base sur un Navigation Graph, qui décrit la navigation de l’application.
Pour créer un Navigation Graph, il faut créer une nouvelle ressource Android de type navigation :
Une fois cette ressource créée, le fichier contient ceci :
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph"> </navigation>
Le Navigation Editor ouvre le fichier :
Le Navigation Editor est composé de 3 grandes parties :
- à gauche, on a accès à la liste des destinations du graphe ;
- au centre, on peut voir et éditer le graphe ;
- à droite, on a accès aux différents attributs des éléments du graphe.
Créer la destination de départ
Pour créer la destination de départ, il suffit de cliquer sur le bouton d’ajout de destination puis de créer un nouveau Fragment via le bouton « Create blank destination » :
Le graphe référence le Fragment créé comme destination de départ à l’aide du tag « app:startDestination » :
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph" app:startDestination="@id/homeFragment"> <fragment android:id="@+id/homeFragment" android:name="fr.akvaternik.navigationcomponent.HomeFragment" android:label="Home" tools:layout="@layout/fragment_home"/> </navigation>
Ajouter le NavHost à l’Activity
Pour finaliser la configuration de la navigation, il reste à créer un NavHost. C’est un conteneur pour le Navigation Graph, qui délègue la navigation du graphe à son NavController (voir plus bas).
Le NavHost fourni par défaut est le NavHostFragment. Il s’agit d’un Fragment dans lequel la navigation va se faire.
Ajoutez ce NavHostFragment dans le layout de l’Activity :
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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"> <fragment android:id="@+id/navHost" android:layout_width="0dp" android:layout_height="0dp" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/nav_graph" app:defaultNavHost="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
En retournant sur le Navigation Editor, on voit que le NavHost apparaît dans la section “HOST” du graphe :
Si le NavHost n’apparaît pas dans la section « HOST », fermez et ré-ouvrez le Navigation Editor.
La navigation est maintenant correctement configurée et le Fragment de départ est bien affiché lorsque l’on lance l’application.
Naviguer vers une destination
Nous allons maintenant voir comment naviguer vers une destination spécifique du graphe.
Ajouter une autre destination
Pour commencer, ajoutez une nouvelle destination au graphe. Le graphe devrait ressembler à celui-ci :
Ajouter une action
La navigation s’effectue via des actions. Une action contient les données nécessaires pour naviguer vers une destination, notamment :
- l’identifiant de la destination ;
- les animations de transition ;
- les arguments à passer à la destination.
Ajoutez une action en effectuant un glisser-déposer depuis l’ancre à droite de homeFragment jusqu’à detailsFragment :
L’action est alors ajoutée au Navigation Graph :
<action android:id="@+id/go_to_details" app:destination="@id/detailsFragment"/>
Naviguer à l’aide d’une action
On va maintenant utiliser l’action créée pour naviguer de HomeFragment à DetailsFragment.
Pour ceci, nous allons utiliser un NavController. Chaque NavHost détient un NavController, dont le rôle est de gérer la navigation dans le NavHost.
Il est possible de récupérer le NavController de différentes façons :
// On an Activity instance. findNavController(R.id.<nav_host_id>) // On a Fragment instance. findNavController() // On a View instance. findNavController()
Pour naviguer vers une destination, il suffit d’utiliser la méthode NavController.navigate en spécifiant l’identifiant de l’action associée :
findNavController().navigate(R.id.go_to_details)
La méthode Navigation.createNavigateOnClickListener apporte également un View.OnClickListener appelant simplement navigate :
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.go_to_details))
Animations de transition
Choisir les animations
Il est possible de choisir les animations de transition pour chaque action.
Pour cela il faut cliquer sur l’action dans le graphe et sélectionner les animations désirées dans la section dédiée à droite :
Animations personnalisées
Si vous souhaitez utiliser vos propres animations, vous pouvez le faire en ajoutant des ressources d’animation dans le projet.
Elles apparaîtront alors lors de la sélection des animations de transition.
Passer des arguments
Méthode traditionnelle
Pour passer un argument à un Fragment, on utilise classiquement un Bundle :
// Set the argument. Bundle().apply { putString("NAME_KEY", "Adrien") } // Retrieve the argument. arguments?.getString("NAME_KEY") ?: throw IllegalArgumentException("No name supplied in arguments.")
Il existe plusieurs problèmes avec cette approche, notamment :
- on peut se tromper de clé ;
- on peut oublier de gérer le cas où l’argument manque.
Plugin Safe Args
Pour régler les problèmes énoncés précédemment, Navigation Architecture Component propose l’utilisation du plugin Safe Args.
Il a pour but de garantir le passage des arguments d’un Fragment à un autre de manière sûre. Pour cela, il génère des classes pour renseigner et récupérer ces arguments.
Pour utiliser le plugin Safe Args :
- ajoutez le classpath du plugin dans le fichier build.gradle de votre projet ;
- appliquez-le dans le fichier build.gradle de votre module.
dependencies { // ... classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha08" }
apply plugin: 'androidx.navigation.safeargs'
Définir un argument
Depuis le graphe, cliquez sur la destination detailsFragment et ajoutez sur la droite un argument « name » de type « string » et de valeur par défaut « world » :
L’argument est ajouté au detailsFragment :
<argument android:name="name" app:argType="string" android:defaultValue="world"/>
Renseigner un argument
Le plugin Safe Args a généré la classe HomeFragmentDirections, permettant d’utiliser l’action R.id.go_to_details en passant éventuellement l’argument « name » :
val directions = HomeFragmentDirections.goToDetails().setName("Adrien") findNavController().navigate(directions)
Récupérer un argument
Le plugin Safe Args a généré la classe DetailsFragmentArgs, permettant d’avoir accès à l’argument « name » depuis un Bundle :
arguments?.let { name = DetailsFragmentArgs.fromBundle(it).name }
Lier la navigation avec des vues
Le Navigation Architecture Component permet de lier la navigation à différentes vues dans le but de :
- visualiser la destination courante ;
- sélectionner une destination.
Bouton Up
Lors d’un clic sur le bouton Up, on peut demander au NavController d’effectuer une navigation up en utilisant la méthode NavController.navigateUp :
override fun onSupportNavigateUp() = navController.navigateUp() || super.onSupportNavigateUp()
ActionBar / Toolbar
Pour lier l’ActionBar à la navigation, on utilise la méthode AppCompatActivity.setupActionBarWithNavController :
setupActionBarWithNavController(navController, drawerLayout)
De façon similaire, pour lier la Toolbar à la navigation, on utilise la méthode Toolbar.setupWithNavController :
toolbar.setupWithNavController(navController, drawerLayout)
Ces méthodes ont pour effet de :
- gérer le bouton Home / Up ;
- gérer le titre de l’ActionBar / Toolbar.
NavigationView
Pour lier la NavigationView à la navigation, on utilise la méthode NavigationView.setupWithNavController :
navigationView.setupWithNavController(navController)
Elle permet de gérer les clics sur les items et met à jour la sélection.
Les identifiants des items du menu doivent être les mêmes que les identifiants des destinations.
BottomNavigationView
Pour lier la BottomNavigationView à la navigation, on utilise la méthode BottomNavigationView.setupWithNavController :
bottomNavigationView.setupWithNavController(navController)
Elle permet de gérer les clics sur les items et met à jour la sélection.
Les identifiants des items du menu doivent être les mêmes que les identifiants des destinations.
Vue personnalisée
Si vous utilisez une vue personnalisée pour la navigation, vous pouvez la lier à la navigation en utilisant :
- NavController.navigate pour naviguer ;
- NavController.addOnDestinationChangedListener pour être notifié qu’une navigation a eu lieu.
// Handle click on custom view item. customViewItem.setOnClickListener { navController.navigate(R.id.<destination_id>) } // Subscribe to navigation events. navController.addOnDestinationChangedListener { controller, destination, arguments -> // Update custom view. }
Deep links
Le Navigation Architecture Component permet également d’effectuer des deep links simplement, tout en créant la backstack automatiquement.
Implicite
Un deep link implicite sera utilisé lors de l’ouverture d’un lien.
Vous pouvez ajouter un deep link depuis le Navigator Editor en sélectionnant la destination voulue et en ajoutant une URI dans la section Deep Links à droite :
Les arguments sont passés entre accolades dans l’URI et seront récupérés par le Fragment de destination.
L’URI peut être par exemple : https:////{}.
Le deep link est ajouté au detailsFragment :
<deepLink app:uri="https://navigationcomponent.akvaternik.fr/details/{name}"/>
[xml]&amp;lt;deepLink app:uri="https://navigationcomponent.akvaternik.fr/details/{name}"/&amp;gt;[/xml]
Il faut également ajouter le tag nav-graph suivant dans AndroidManifest.xml pour que les tags intent-filter gérant les deep links soient générés :
<activity android:name=".MainActivity"> // ... <nav-graph android:value="@navigation/nav_graph" /> </activity>
Explicite
Un deep link explicite sera utilisé lors du clic sur une notification ou un widget.
Vous pouvez créer un PendingIntent correspond au deep link à l’aide du NavDeepLinkBuilder :
val args = DetailsFragmentArgs.Builder() .setName("Bob") .build() val pendingIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) .setDestination(R.id.detailsFragment) .setArguments(args.toBundle()) .createPendingIntent()
Destination personnalisée
Le NavController utilise un ou plusieurs Navigator pour effectuer les opérations de navigation.
Chaque Navigator définit son type de destination et doit gérer sa backstack quand on navigue entre 2 destinations lui appartenant.
Par défaut, le Navigation Architecture Component ne supporte que les destinations qui sont de type Fragment ou Activity, mais il est possible d’ajouter des nouveaux types de destination.
Pour cela, il faut créer un nouveau Navigator :
@Navigator.Name("custom") class MyNavigator : Navigator<MyNavigator.Destination>() { override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Extras?): NavDestination? { // Perform navigation here. return null } override fun createDestination(): Destination { return Destination(this) } override fun popBackStack(): Boolean { return false } class Destination(navigator: MyNavigator) : NavDestination(navigator) }
Puis l’ajouter au NavigatorProvider du NavController et inflater le graphe :
val myNavigator = MyNavigator() navController.navigatorProvider += myNavigator val graph = navController.navInflater.inflate(R.navigation.nav_graph) navController.graph = graph
Également, puisqu’on inflate le graphe manuellement, il faut retirer l’attribut « app:navGraph » de NavHostFragment dans le layout de l’Activity.
On peut maintenant ajouter des tags « custom » dans le graphe, « custom » étant la valeur de l’argument passé à l’annotation @Navigator.Name :
<navigation ...> // ... <custom android:id="@+id/customDestination" /> </navigation>
Malgré la présence de l’annotation @Navigator.Name(« custom »), il est possible qu’un warning apparaisse lors de l’ajout du tag « custom ». Dans ce cas, redémarrez Android Studio.
Tests
Puisque le Navigation Architecture Component s’occupe de la navigation, il est possible de tester la logique d’une application, sans se soucier des difficultés liées à la navigation.
Par exemple, on va tester que lorsqu’on clique sur le bouton de HomeFragment, on navigue bien via l’action « go_to_details » en passant les bons arguments.
Importer les dépendances Gradle
Ajoutez les dépendances suivantes dans le fichier build.gradle de votre module :
dependencies { // ... implementation 'androidx.fragment:fragment:1.1.0-alpha02' debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha02' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' androidTestImplementation 'io.mockk:mockk-android:1.8.13.kotlin13' }
Ajouter le test
Créez le fichier HomeFragmentTest.kt dans src/androidTest et ajoutez-y un test comme ceci :
class HomeFragmentTest { @Test fun navigate_when_button_clicked() { } }
Dans l’ordre, nous allons :
- lancer le HomeFragment en utilisant un FragmentScenario ;
- créer un mock de NavController avec MockK ;
- injecter ce mock en tant que NavController du HomeFragment ;
- cliquer sur le bouton du HomeFragment ;
- vérifier qu’on appelle la méthode navigate du NavController avec les bons arguments.
Voici le code que l’on peut utiliser :
@Test fun navigate_when_button_clicked() { // Given val scenario = launchFragmentInContainer<HomeFragment>() val navController = mockk<NavController>(relaxed = true) scenario.onFragment { fragment -> Navigation.setViewNavController(fragment.view!!, navController) } // When onView(withId(R.id.button)).perform(click()) // Then val directions = HomeFragmentDirections.goToDetails().setName("Adrien") verify { navController.navigate(directions) } }
Pour aller plus loin
Sources
Retrouvez le projet Navigation Architecture Component sur GitHub.
Liens utiles
Documentation (developer.android.com)
Android Jetpack: manage UI navigation with Navigation Controller (Google I/O ’18)
Single Activity: Why, When, and How (Android Dev Summit ’18)
Conclusion
Bien que le Navigation Architecture Component ne soit aujourd’hui disponible qu’en version alpha et comporte quelques bugs, il répond déjà à de nombreuses problématiques rencontrées lors du développement d’une application Android.
Son utilisation permet de séparer efficacement la logique de navigation (quand naviguer, quels arguments passer) de son implémentation (comment elle s’effectue).
De plus, la mise à disposition du Navigation Editor donne au développeur un lieu centralisé où il peut voir toute la navigation et l’éditer de manière simple et déclarative.
Enfin, le fait que la couche de navigation soit remplaçable facilement par des mocks est un vrai plus pour assurer la testabilité des destinations de l’application.
Commentaire
0 réponses pour " Android : Navigation Architecture Component "
Published by Lolfish , Il y a 3 ans
Hello
Merci beaucoup pour cet aperçu, un des rares en français o_o.
Je suis actuellement confrontée à un problème :
J’ai un navgraph un peu comme ca :
StartDestination Home->NestedGraph Account-> NestedGraph Contacts.
Il y a d’autres items dans le schema mais c’est celui-ci qui m’intéresse.
J’ai une bottomnavigationview dans laquelle j’ai 5 items, dont home et account, mais pas contacts.
Sur mon home fragment, j’ai un lien vers contacts, MAIS :
– Soit je sors contacts de mon nestedgraph et j’ai une action simple home->contacts, mais dans ce cas évidemment bah pour ma bottomnavigationview l’item coloré reste home…
– soit je mets une global action mais dans ce cas ca me retourne une exception comme quoi aucun navcontroller n’a été trouvé /defini pour ça.
Et sinon mes boutons » back »dans ma toolbar (qui est censee etre une noactionbar mais ils apparaissent quand meme) ne fonctionnent pas, est ce que c’est à moi de gérer le click ? ca marche avec le bouton back du telephone mais pas l’ icone de la toolbar, rien ne se passe meme si je regle l’option popup to sur chaque action de mon navgraph.
Bref je suis un peu perdue avec tout ça surtout quand tous les tutos sont en Kotlin et en anglais alors que je dois coder en java :'(. J’ ai essayé avec un viewpager j’ai cassé tout mon code. Bref déjà merci pour ces explications très claires en français :) :)