Accéder à une destination

La navigation vers une destination est effectuée à l'aide d'un objet NavController, qui gère la navigation dans l'application via un NavHost. Chaque NavHost a son propre NavController correspondant. NavController propose plusieurs façons d'accéder à une destination, qui sont décrites plus en détail dans les sections ci-dessous.

Afin de récupérer NavController pour un fragment, une activité ou une vue, utilisez l'une des méthodes suivantes :

Kotlin

Java

Après avoir récupéré un NavController, vous pouvez appeler l'une des surcharges de navigate() pour naviguer entre les destinations. Chaque surcharge est compatible avec divers scénarios de navigation, comme décrit dans les sections suivantes.

Utiliser Safe Args pour naviguer de manière sécurisée

Pour naviguer entre les destinations, nous vous recommandons d'utiliser le plug-in Gradle Safe Args. Ce plug-in génère des classes d'objets et de compilateurs simples qui permettent une navigation sécurisée entre les destinations. Safe Args est recommandé à la fois pour la navigation et la transmission de données entre des destinations.

Pour ajouter Safe Args à votre projet, incluez le classpath suivant dans votre fichier build.gradle de premier niveau :

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.5.3"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.5.3"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

Vous devez également appliquer l'un des deux plug-ins disponibles.

Pour générer un code en langage Java adapté aux modules en Java ou mixant du Java et du Kotlin, ajoutez la ligne suivante au fichier build.gradle de votre application ou module :

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Sinon, pour générer un code Kotlin adapté aux modules exclusivement en Kotlin, ajoutez la ligne suivante :

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

android.useAndroidX=true doit figurer dans votre fichier gradle.properties, tel qu'indiqué sur la page Migrer vers AndroidX.

Une fois que vous avez activé Safe Args, votre code généré contient des classes et des méthodes pour chaque action que vous avez définie, ainsi que des classes correspondant à chaque destination d'envoi et de réception.

Safe Args génère une classe pour chaque destination à l'origine d'une action. Le nom de classe généré ajoute "Directions" au nom de la classe de destination à l'origine de l'action. Par exemple, si la destination à l'origine de l'action est SpecifyAmountFragment, la classe générée est nommée SpecifyAmountFragmentDirections.

La classe générée contient une méthode statique pour chaque action définie dans la destination à l'origine de l'action. Cette méthode accepte tous les paramètres d'action définis comme arguments et renvoie un objet NavDirections que vous pouvez transmettre directement à navigate().

Exemple Safe Args

Supposons que nous ayons un graphe de navigation avec une seule action qui relie deux destinations, SpecifyAmountFragment et ConfirmationFragment. ConfirmationFragment accepte un seul paramètre float que vous fournissez dans le cadre de l'action.

Safe Args génère une classe SpecifyAmountFragmentDirections avec une seule méthode, actionSpecifyAmountFragmentToConfirmationFragment(), et une classe interne appelée ActionSpecifyAmountFragmentToConfirmationFragment. La classe interne est dérivée de NavDirections et stocke l'ID d'action associé et le paramètre float. L'objet NavDirections renvoyé peut ensuite être transmis directement à navigate(), comme illustré dans l'exemple suivant :

Kotlin

override fun onClick(v: View) {
    val amount: Float = ...
    val action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount)
    v.findNavController().navigate(action)
}

Java

@Override
public void onClick(View view) {
    float amount = ...;
    action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount);
    Navigation.findNavController(view).navigate(action);
}

Pour en savoir plus sur la transmission de données entre des destinations avec Safe Args, consultez la section Utiliser Safe Args pour transmettre des données en toute sécurité.

Naviguer à l'aide d'un ID

navigate(int) accepte l'ID de ressource d'une action ou d'une destination. L'extrait de code suivant montre comment accéder à ViewTransactionsFragment :

Kotlin

viewTransactionsButton.setOnClickListener { view ->
   view.findNavController().navigate(R.id.viewTransactionsAction)
}

Java

viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
    }
});

Pour les boutons, il existe trois variantes de Navigation.createNavigateOnClickListener(). Ces variantes sont utiles avec le langage de programmation Java. Si vous utilisez Kotlin, OnClickListener est une interface SAM. Vous pouvez donc utiliser un lambda de fin. Cette approche peut être plus courte et plus facile à lire que d'appeler directement createNavigateOnClickListener().

Pour gérer d'autres composants d'interface utilisateur courants, tels que la barre d'application supérieure et la barre de navigation inférieure, consultez la section Mettre à jour les composants d'interface utilisateur avec NavigationUI.

Lorsque vous définissez une action dans le graphe de navigation, le composant Navigation génère une classe NavAction correspondante, qui contient les configurations définies pour cette action, y compris les suivantes :

  • Destination : ID de ressource de la destination cible.
  • Arguments par défaut : android.os.Bundle contenant les valeurs par défaut de la destination cible, le cas échéant.
  • Options de navigation : options de navigation représentées par NavOptions. Cette classe contient toutes les configurations spéciales permettant d'effectuer une transition vers et depuis la destination cible, y compris la configuration des ressources d'animation, le comportement de suppression de la pile et le lancement ou non de la destination en mode haut simple.

Examinons un exemple de graphe composé de deux écrans, accompagnés d'une action permettant de passer de l'un à l'autre :

<?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/a">

    <fragment android:id="@+id/a"
              android:name="com.example.myapplication.FragmentA"
              android:label="a"
              tools:layout="@layout/a">
        <action android:id="@+id/action_a_to_b"
                app:destination="@id/b"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>

    <fragment android:id="@+id/b"
              android:name="com.example.myapplication.FragmentB"
              android:label="b"
              tools:layout="@layout/b">
        <action android:id="@+id/action_b_to_a"
                app:destination="@id/a"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"
                app:popUpTo="@+id/a"
                app:popUpToInclusive="true"/>
    </fragment>
</navigation>

Lorsque le graphe de navigation est gonflé, ces actions sont analysées, et les objets NavAction correspondants sont générés avec les configurations définies dans le graphe. Par exemple, action_b_to_a est défini comme allant de la destination b à la destination a. L'action inclut des animations ainsi que le comportement popTo qui supprime toutes les destinations de la pile "Retour". Tous ces paramètres sont enregistrés en tant que NavOptions et sont associés à l'élément NavAction.

Pour suivre cet élément NavAction, utilisez NavController.navigate() en transmettant l'ID de l'action, comme indiqué dans l'exemple suivant :

Kotlin

findNavController().navigate(R.id.action_b_to_a)

Java

NavigationHostFragment.findNavController(this).navigate(R.id.action_b_to_a);

Appliquer les options de navigation par programmation

Les exemples précédents montrent comment spécifier NavOptions dans le fichier XML du graphe de navigation. Cependant, certaines options peuvent varier en fonction de contraintes inconnues au moment de la compilation. Dans ce cas, les options de navigation (NavOptions) doivent être créées et définies de manière programmatique, comme indiqué dans l'exemple suivant :

Kotlin

findNavController().navigate(
    R.id.action_fragmentOne_to_fragmentTwo,
    null,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        R.id.action_fragmentOne_to_fragmentTwo,
        null,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

Cet exemple utilise une forme étendue de navigate() et contient des arguments Bundle et NavOptions supplémentaires. Toutes les variantes de navigate() ont des versions étendues qui acceptent un argument NavOptions.

Vous pouvez également appliquer NavOptions de manière programmatique lorsque vous accédez à des liens profonds implicites :

Kotlin

findNavController().navigate(
    deepLinkUri,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        deepLinkUri,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

Cette variante de navigate() utilise un Uri pour le lien profond implicite, ainsi que l'instance NavOptions.

Naviguer à l'aide de DeepLinkRequest

navigate(NavDeepLinkRequest) vous permet d'accéder directement à une destination implicite de lien profond, comme illustré dans l'exemple suivant :

Kotlin

val request = NavDeepLinkRequest.Builder
    .fromUri("android-app://androidx.navigation.app/profile".toUri())
    .build()
findNavController().navigate(request)

Java

NavDeepLinkRequest request = NavDeepLinkRequest.Builder
    .fromUri(Uri.parse("android-app://androidx.navigation.app/profile"))
    .build()
NavHostFragment.findNavController(this).navigate(request)

En plus de l'Uri, NavDeepLinkRequest accepte également les liens profonds avec les actions et les types MIME. Pour ajouter une action à la requête, utilisez fromAction() ou setAction(). Pour ajouter un type MIME à une requête, utilisez fromMimeType() ou setMimeType().

Pour qu'une propriété NavDeepLinkRequest soit correctement associé à une destination implicite de lien profond, l'URI, l'action et le type MIME doivent tous correspondre à l'élément NavDeepLink dans la destination. Les URI doivent correspondre au modèle, les actions doivent être une correspondance exacte et les types MIME doivent être liés (par exemple, "image/jpg" correspond à "image/*").

Contrairement à la navigation à l'aide d'ID d'action ou de destination, vous pouvez accéder à n'importe quel lien profond de votre graphe, que la destination soit visible ou non. Vous pouvez accéder à une destination sur le graphe actuel ou sur un graphe complètement différent.

Lorsque vous utilisez NavDeepLinkRequest, la pile "Retour" n'est pas réinitialisée. Ce comportement est différent de la navigation à l'aide de liens profonds, où la pile "Retour" est remplacée lors de la navigation. popUpTo et popUpToInclusive suppriment toujours les destinations de la pile "Retour", comme si vous aviez utilisé un ID.

Navigation et pile "Retour"

Android gère une pile "Retour" qui contient les destinations que vous avez consultées. La première destination de votre application est placée dans la pile lorsque l'utilisateur ouvre l'application. Chaque appel de la méthode navigate() place une autre destination au-dessus de la pile. Appuyez sur Haut ou Retour pour appeler les méthodes NavController.navigateUp() et NavController.popBackStack(), respectivement, afin de supprimer la destination principale de la pile.

NavController.popBackStack() renvoie une valeur booléenne indiquant s'il a bien renvoyé une autre destination. Le cas le plus courant d'affichage de la valeur false est lorsque vous supprimez manuellement de la pile la destination de départ du graphe.

Lorsque la méthode renvoie false, NavController.getCurrentDestination() renvoie null. Vous êtes responsable de la navigation vers une nouvelle destination ou de la gestion de la suppression de la pile en appelant finish() au niveau de l'activité, comme illustré dans l'exemple suivant :

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

Les destinations de boîte de dialogue mettent en œuvre l'interface FloatingWindow, ce qui indique qu'elles se superposent aux autres destinations sur la pile "Retour". Par conséquent, une ou plusieurs destinations FloatingWindow ne peuvent être présentes qu'en haut de la pile "Retour" de navigation. Si vous accédez à une destination qui n'implémente pas FloatingWindow, toutes les destinations FloatingWindow sont automatiquement supprimées du haut de la pile. Cette approche garantit que la destination actuelle est toujours entièrement visible par rapport aux autres destinations de la pile "Retour".

Par exemple, si la pile "Retour" comporte uniquement des destinations non flottantes et que l'utilisateur accède à une destination Dialog, elle peut se présenter comme illustré dans la figure 1 :

pile "Retour" avec une destination de boîte de dialogue en haut
Figure 1. Pile "Retour" avec une destination Dialog en haut

Si l'utilisateur accède ensuite à une autre destination Dialog, celle-ci est ajoutée en haut de la pile "Retour", comme illustré dans la figure 2 :

Pile "Retour" avec deux destinations "Dialog" en haut
Figure 2. Pile "Retour" avec deux destinations Dialog en haut

Si l'utilisateur accède ensuite à une destination non flottante, toutes les destinations FloatingWindow sont d'abord supprimées du haut de la pile "Retour" avant d'atteindre la nouvelle destination, comme illustré dans la figure 3 :

les destinations de boîte de dialogue sont supprimées de la pile, et la nouvelle destination est ajoutée
Figure 3. Les destinations Dialog sont supprimées de la pile, et la nouvelle destination est ajoutée

popUpTo et popUpToInclusive

Lorsque vous utilisez une action, vous pouvez éventuellement supprimer des destinations supplémentaires de la pile "Retour". Par exemple, si votre application implique un processus d'authentification initial, une fois qu'un utilisateur s'est connecté, vous devez supprimer toutes les destinations liées à l'authentification de la pile "Retour" afin que le bouton "Retour" ne redirige pas les utilisateurs vers ce processus.

Pour afficher des destinations lorsque vous passez d'une destination à une autre, ajoutez un attribut app:popUpTo à l'élément <action> associé. app:popUpTo indique à la bibliothèque Navigation de supprimer certaines destinations de la pile "Retour" dans le cadre de l'appel de navigate(). La valeur de l'attribut est l'ID de la destination la plus récente qui doit rester dans la pile.

Vous pouvez également inclure app:popUpToInclusive="true" pour indiquer que la destination spécifiée dans app:popUpTo doit également être supprimée de la pile "Retour".

Exemple avec popUpTo : logique circulaire

Imaginons que votre application comporte trois destinations (A, B et C), ainsi que des actions menant de A à B, de B à C et de C à A. Le graphe de navigation correspondant est illustré à la figure 4 :

Figure 4. Graphe de navigation circulaire avec trois destinations : A, B et C

À chaque action de navigation, une destination est ajoutée à la pile "Retour". Si vous répétiez ce processus, la pile "Retour" contiendrait plusieurs ensembles de chaque destination (A, B, C, A, B, C, A, etc.). Pour éviter cette répétition, vous pouvez spécifier app:popUpTo et app:popUpToInclusive dans l'action qui vous emmène de la destination C à la destination A, comme illustré dans l'exemple suivant :

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_a"
        app:destination="@id/a"
        app:popUpTo="@+id/a"
        app:popUpToInclusive="true"/>
</fragment>

Une fois la destination C atteinte, la pile "Retour" contient une instance de chaque destination (A, B, C). Pour revenir à la destination A, nous ajoutons également popUpTo A. B et C seront donc supprimés de la pile pendant la navigation. Avec app:popUpToInclusive="true", nous supprimons également ce premier A de la pile et nous l'effaçons. Notez que si vous n'utilisez pas app:popUpToInclusive, la pile "Retour" contiendra deux instances de la destination A.

popUpToSaveState et restoreSaveState

Lorsque vous utilisez app:popUpTo pour naviguer vers une destination, Navigation 2.4.0-alpha01 et les versions ultérieures vous permettent d'enregistrer les états de toutes les destinations la pile "Retour". Pour activer cette option, incluez app:popUpToSaveState="true" dans l'élément <action> associé :

<action
  android:id="@+id/action_c_to_a"
  app:destination="@id/a"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:popUpToSaveState="true"/>

Lorsque vous accédez à une destination, vous pouvez également inclure app:restoreSaveState="true" pour restaurer automatiquement l'état qui lui est associé dans app:destination.