Navigation avec Jetpack Compose

1. Introduction

Dernière mise à jour : 25/07/2022

Ce dont vous avez besoin

Navigation est une bibliothèque Jetpack qui permet de naviguer d'une destination à une autre dans votre application. La bibliothèque Navigation fournit également un artefact particulier pour permettre une navigation cohérente et unique avec Jetpack Compose. Cet artefact (navigation-compose) est le sujet central de cet atelier de programmation.

Objectifs de l'atelier

Vous allez vous servir de l'étude Rally Material comme base de cet atelier de programmation pour implémenter le composant Navigation dans Jetpack et activer la navigation entre les écrans Rally composables.

Points abordés

  • Principes de base de l'utilisation de la bibliothèque Navigation de Jetpack avec Jetpack Compose
  • Navigation d'un composable à un autre
  • Intégrer un composable de barre d'onglets personnalisée à votre hiérarchie de navigation
  • Naviguer avec des arguments
  • Navigation à l'aide de liens profonds
  • Test de la navigation

2. Configurer

Pour continuer, clonez le point de départ (branche main) de l'atelier de programmation.

$ git clone https://github.com/googlecodelabs/android-compose-codelabs.git

Vous pouvez également télécharger deux fichiers ZIP :

Maintenant que vous avez téléchargé le code, ouvrez le projet NavigationCodelab dans Android Studio. Vous êtes prêt à commencer.

3. Présentation de l'application Rally

Pour commencer, vous devez vous familiariser avec l'application Rally et son codebase. Exécutez l'application et explorez-la un peu.

Rally possède trois écrans principaux sous forme de composables :

  1. OverviewScreen : présentation de toutes les alertes et transactions financières
  2. AccountsScreen : informations sur les comptes existants
  3. BillsScreen : dépenses planifiées

Capture de l'écran Overview (Aperçu) contenant des informations sur les alertes, les comptes et les factures. Capture de l'écran Accounts (Comptes), contenant des informations sur plusieurs comptes. Capture de l'écran "Bills" (Factures) contenant des informations sur plusieurs factures à régler.

Tout en haut de l'écran, Rally affiche un composable de barre d'onglets personnalisée (RallyTabRow) qui permet de naviguer entre ces trois écrans. Appuyez sur chaque icône pour développer la sélection et accéder à l'écran correspondant :

336ba66858ae3728.png e26281a555c5820d.png

Lorsque vous accédez à ces écrans composables, considérez-les comme des destinations de navigation, car notre objectif est d'atterrir sur chacun d'eux à un point spécifique. Ces destinations sont prédéfinies dans le fichier RallyDestinations.kt.

Vous y trouverez les trois principales destinations définies en tant qu'objets (Overview, Accounts et Bills), ainsi qu'un objet SingleAccount qui sera ajouté plus tard à l'application. Chaque objet est basé sur l'interface RallyDestination et contient les informations requises au sujet de chaque destination, à des fins de navigation :

  1. Un élément icon pour la barre supérieure
  2. Une chaîne route (requise pour la navigation dans Compose, et qui sera le chemin menant à cette destination)
  3. Un screen représentant l'intégralité du composable pour cette destination

En exécutant l'application, vous remarquerez que la barre supérieure vous permet de naviguer entre les destinations. Cependant, l'application n'utilise pas la navigation Compose, mais son mécanisme de navigation actuel repose sur un changement manuel de composables et déclenche une recomposition pour afficher le nouveau contenu. L'objectif de cet atelier de programmation est donc de migrer et d'implémenter la navigation Compose.

4. Migrer vers la navigation dans Compose

La migration de base vers Jetpack Compose comprend plusieurs étapes :

  1. Ajouter la dernière dépendance Compose Navigation
  2. Configurer le NavController
  3. Ajouter un NavHost et créer le graphique de navigation
  4. Préparer les itinéraires pour naviguer entre les différentes destinations de l'application
  5. Remplacer le mécanisme de navigation actuel par la navigation Compose

Examinons chacune de ces étapes plus en détail.

Ajouter la dépendance de navigation

Ouvrez le fichier de compilation de l'application, qui se trouve ici : app/build.gradle. Dans la section des dépendances, ajoutez la dépendance navigation-compose.

dependencies {
  implementation "androidx.navigation:navigation-compose:{latest_version}"
  // ...
}

Pour accéder à la dernière version de cette dépendance, cliquez ici.

Synchronisez le projet. Vous êtes prêt à utiliser Navigation dans Compose.

Configurer NavController

Le composant NavController est au cœur de la navigation dans Compose. Il effectue le suivi des entrées composables de la pile "Retour", fait avancer la pile, permet de manipuler la pile "Retour" et de naviguer entre les états de destination. NavController est essentiel pour la navigation : vous devez d'abord le créer pour configurer la navigation avec Compose.

Pour obtenir un NavController, appelez la fonction rememberNavController(). Le composant NavController est alors créé et mémorisé. Il survit aux modifications de configuration (en utilisant rememberSaveable).

Vous devez toujours créer et placer le NavController au premier niveau de votre hiérarchie de composables, généralement dans votre composable App. Ainsi, tous les composables qui doivent faire référence au NavController y ont accès. Ce processus suit les principes du hissage d'état et garantit que NavController est la principale source d'informations fiable qui permettra de naviguer d'un écran composable à un autre et maintenir la pile "Retour".

Ouvrez RallyActivity.kt. Récupérez le NavController en utilisant rememberNavController() dans RallyApp, car il s'agit du composable racine et du point d'entrée de l'application entière :

import androidx.navigation.compose.rememberNavController
// ...

@Composable
fun RallyApp() {
    RallyTheme {
        var currentScreen: RallyDestination by remember { mutableStateOf(Overview) }
        val navController = rememberNavController()
        Scaffold(
            // ...
        ) {
            // ...
       }
}

Itinéraires avec la navigation Compose

Comme nous l'avons déjà indiqué, l'application Rally comporte trois destinations principales, dont une qui sera ajoutée ultérieurement (SingleAccount). Elles sont définies dans RallyDestinations.kt, et nous avons mentionné que chaque destination possède une icon, une route et une screen définies :

Capture de l'écran Overview (Aperçu) contenant des informations sur les alertes, les comptes et les factures. Capture de l'écran Accounts (Comptes), contenant des informations sur plusieurs comptes. Capture de l'écran "Bills" (Factures) contenant des informations sur plusieurs factures à régler.

L'étape suivante consiste à ajouter ces destinations à votre graphique de navigation, avec Overview comme destination de départ au lancement de l'application.

Lorsque vous utilisez Navigation dans Compose, chaque destination composable de votre graphique de navigation est associée à un itinéraire. Les itinéraires sont représentés par des chaînes qui définissent le chemin d'accès à votre composable et qui guident votre navController vers la bonne destination. Vous pouvez le considérer comme un lien profond implicite menant à une destination spécifique. Chaque destination doit avoir un itinéraire unique.

Pour ce faire, nous allons utiliser la propriété route de chaque objet RallyDestination. Par exemple, Overview.route est l'itinéraire qui vous mènera au composable de l'écran Overview.

Appeler le composant NavHost avec le graphique de navigation

L'étape suivante consiste à ajouter un NavHost et à créer votre graphique de navigation.

Les trois principaux objets de navigation sont NavController, NavGraph et NavHost. Le NavController est toujours associé à un seul composable NavHost. NavHost sert de conteneur et affiche la destination actuelle du graphique. Lorsque vous naviguez entre les composables, le contenu de NavHost est automatiquement recomposé. Il associe également NavController à un graphique de navigation (NavGraph) qui mappe la destination des composables. Il s'agit d'un ensemble de destinations récupérables.

Revenez au composable RallyApp dans RallyActivity.kt. Remplacez le composable Box dans Scaffold, qui contient le contenu de l'écran actuel pour le changement manuel des écrans, par un nouveau NavHost que vous pouvez créer en suivant l'exemple ci-dessous.

Transmettez le navController que nous avons créé à l'étape précédente pour le rattacher à NavHost. Comme indiqué précédemment, chaque NavController doit être associé à un seul NavHost.

NavHost a également besoin d'un itinéraire startDestination pour savoir quelle destination afficher lorsque l'application est lancée. Définissez-le donc sur Overview.route. Transmettez également un Modifier pour accepter la marge extérieure Scaffold et l'appliquer à NavHost.

Le dernier paramètre builder: NavGraphBuilder.() -> Unit est chargé de définir et de créer le graphique de navigation. Comme il utilise la syntaxe lambda de la navigation Kotlin DSL, il peut être transmis en tant que lambda finale dans le corps de la fonction et extrait des parenthèses :

import androidx.navigation.compose.NavHost
...

Scaffold(...) { innerPadding ->
    NavHost(
        navController = navController,
        startDestination = Overview.route,
        modifier = Modifier.padding(innerPadding)
    ) {
       // builder parameter will be defined here as the graph
    }
}

Ajouter des destinations au NavGraph

Vous pouvez maintenant définir votre graphique de navigation et les destinations vers lesquelles NavController peut naviguer. Comme mentionné précédemment, le paramètre builder attend une fonction. Navigation Compose fournit donc la fonction d'extension NavGraphBuilder.composable pour ajouter facilement des destinations composables individuelles au graphique de navigation et définir les informations de navigation requises.

La première destination sera Overview. Vous devez donc l'ajouter via la fonction d'extension composable et définir sa chaîne unique route. Ainsi, la destination est ajoutée au graphique de navigation. Vous devez donc également définir l'interface utilisateur à afficher lorsque vous accédez à cette destination. Cette opération sera également effectuée à l'aide d'un lambda de fin dans le corps de la fonction composable, une logique fréquemment utilisée dans Compose :

import androidx.navigation.compose.composable
// ...

NavHost(
    navController = navController,
    startDestination = Overview.route,
    modifier = Modifier.padding(innerPadding)
) {
    composable(route = Overview.route) {
        Overview.screen()
    }
}

En suivant ce schéma, nous allons ajouter les trois composables d'écran principal pour les trois destinations :

NavHost(
    navController = navController,
    startDestination = Overview.route,
    modifier = Modifier.padding(innerPadding)
) {
    composable(route = Overview.route) {
        Overview.screen()
    }
    composable(route = Accounts.route) {
        Accounts.screen()
    }
    composable(route = Bills.route) {
        Bills.screen()
    }
}

Exécutez maintenant l'application. Overview est la destination de départ, et l'interface utilisateur correspondante s'affiche.

Comme mentionné précédemment, une barre d'onglets personnalisée, RallyTabRow composable, gérait auparavant la navigation manuelle entre les écrans. À ce stade, elle n'est pas encore connectée à la nouvelle navigation. Vous pouvez donc vérifier que l'action de clic sur les onglets ne modifiera pas la destination du composable affiché. Remédions à ce problème dans la suite de cet atelier !

5. Intégrer RallyTabRow à la navigation

Au cours de cette étape, vous allez connecter RallyTabRow à navController et au graphique de navigation pour lui permettre d'accéder aux destinations souhaitées.

Pour ce faire, vous devez utiliser votre nouveau navController afin de définir l'action de navigation appropriée pour le rappel onTabSelected de RallyTabRow. Ce rappel définit l'action à déclencher en cas de sélection d'une icône d'onglet spécifique, et effectue l'action de navigation via l'itinéraire navController.navigate(route)..

En suivant ces conseils, dans RallyActivity, recherchez le composable RallyTabRow et son paramètre de rappel onTabSelected.

Puisque nous souhaitons que l'onglet atteigne une destination spécifique suite au clic de l'utilisateur, vous devez également savoir quelle icône d'onglet a été sélectionnée. Heureusement, le paramètre onTabSelected: (RallyDestination) -> Unit fournit déjà cette information. Vous utiliserez ces informations et l'itinéraire RallyDestination pour guider votre navController et appeler navController.navigate(newScreen.route) lorsqu'un onglet est sélectionné :

@Composable
fun RallyApp() {
    RallyTheme {
        var currentScreen: RallyDestination by remember { mutableStateOf(Overview) }
        val navController = rememberNavController()
        Scaffold(
            topBar = {
                RallyTabRow(
                    allScreens = rallyTabRowScreens,
                    // Pass the callback like this,
                    // defining the navigation action when a tab is selected:
                    onTabSelected = { newScreen ->
                        navController.navigate(newScreen.route)
                    },
                    currentScreen = currentScreen,
                )
            }

Si vous exécutez l'application maintenant, vous pouvez vérifier qu'un appui sur des onglets spécifiques dans RallyTabRow permet d'accéder à la bonne destination de composable. Toutefois, vous avez peut-être remarqué deux problèmes :

  1. Lorsque vous appuyez plusieurs fois sur le même onglet, plusieurs copies de la même destination se lancent.
  2. L'interface utilisateur de l'onglet ne correspond pas à la destination affichée. Cela signifie que le développement et la réduction des onglets sélectionnés ne fonctionnent pas comme prévu :

336ba66858ae3728.png e26281a555c5820d.png

Résolvons à présent ces deux problèmes.

Lancer une copie unique d'une destination

Pour résoudre le premier problème et s'assurer qu'une seule copie d'une destination donnée est placée en haut de la pile "Retour", l'API Compose Navigation fournit un indicateur launchSingleTop que vous pouvez transmettre à votre navController.navigate(), comme suit :

navController.navigate(route) { launchSingleTop = true }

Ce comportement doit s'appliquer à l'ensemble de l'application. Ainsi, pour chaque destination, au lieu de copier et coller cet indicateur dans tous vos appels navigate(...), vous pouvez l'extraire dans une extension d'assistance en bas de votre RallyActivity :

import androidx.navigation.NavHostController
// ...

fun NavHostController.navigateSingleTopTo(route: String) =
    this.navigate(route) { launchSingleTop = true }

Vous pouvez maintenant remplacer l'appel navController.navigate(newScreen.route) par navigateSingleTopTo(...). Exécutez de nouveau l'application et vérifiez que vous ne recevez qu'une seule copie d'une seule destination lorsque vous cliquez plusieurs fois sur son icône dans la barre supérieure :

@Composable
fun RallyApp() {
    RallyTheme {
        var currentScreen: RallyDestination by remember { mutableStateOf(Overview) }
        val navController = rememberNavController()
        Scaffold(
            topBar = {
                RallyTabRow(
                    allScreens = rallyTabRowScreens,
                    onTabSelected = { newScreen ->
                        navController
                            .navigateSingleTopTo(newScreen.route)
                    },
                    currentScreen = currentScreen,
                )
            }

Contrôler les options de navigation et l'état de la pile "Retour"

En plus de launchSingleTop, d'autres options sont disponibles dans NavOptionsBuilder pour contrôler et personnaliser le comportement de votre navigation. Puisque RallyTabRow fonctionne de la même manière que BottomNavigation, vous devez également déterminer si vous souhaitez enregistrer et restaurer un état de destination lorsque vous naviguez vers et depuis celui-ci. Par exemple, si vous faites défiler la page "Overview" (Aperçu) vers le bas, puis que vous naviguez vers "Accounts" (Comptes) et revenez en arrière, voulez-vous conserver la position du défilement ? Voulez-vous appuyer de nouveau sur la même destination dans le RallyTabRow pour actualiser l'état de votre écran ? Ce sont des questions légitimes, et vous devez pour appuyer sur les exigences propres à la conception de votre application.

Nous allons aborder quelques options supplémentaires que vous pouvez utiliser dans la même fonction d'extension navigateSingleTopTo :

  • launchSingleTop = true : comme indiqué précédemment, cette option vous permet de garantir qu'il n'y aura pas plus d'une copie d'une destination spécifique en haut de la pile "Retour".
  • Ainsi, dans l'application Rally, appuyer plusieurs fois sur le même onglet ne lancera pas plusieurs copies de la même destination.
  • popUpTo(startDestination) { saveState = true } : permet d'afficher la destination de départ du graphique afin d'éviter de placer une grande pile de destinations sur la pile "Retour" lorsque vous sélectionnez des onglets.
  • Dans Rally, cela signifie que si vous appuyez sur la flèche de retour depuis n'importe quelle destination, la pile "Retour" sera affichée dans "Overview" (Aperçu).
  • restoreState = true : détermine si cette action de navigation doit restaurer tout état enregistré précédemment par PopUpToBuilder.saveState ou par l'attribut popUpToSaveState. Si aucun état n'a été précédemment enregistré avec l'ID de destination, cela n'a aucun effet.
  • Dans Rally, cela signifie que si vous appuyez de nouveau sur le même onglet, les données et l'état de l'utilisateur précédents s'affichent à l'écran, sans que vous ayez besoin de le charger à nouveau.

Vous pouvez ajouter toutes ces options une à une dans le code, exécuter l'application après chaque ajout, et vérifier le comportement de l'application après l'ajout de chaque indicateur. Ainsi, vous pourrez voir comment concrètement chaque indicateur modifie l'état de la navigation et de la pile "Retour" :

import androidx.navigation.NavHostController
import androidx.navigation.NavGraph.Companion.findStartDestination
// ...

fun NavHostController.navigateSingleTopTo(route: String) =
    this.navigate(route) {
        popUpTo(
            this@navigateSingleTopTo.graph.findStartDestination().id
        ) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
}

Corriger l'interface utilisateur des onglets

Au tout début de l'atelier de programmation, lorsque vous utilisiez encore la navigation manuelle, RallyTabRow utilisait la variable currentScreen pour déterminer si chaque onglet devait être développé ou réduit.

Cependant, currentScreen ne sera plus mis à jour une fois les modifications effectuées. C'est pourquoi la fonctionnalité permettant de développer et de réduire les onglets sélectionnés dans RallyTabRow ne fonctionne plus.

Pour réactiver ce comportement avec la navigation Compose, vous devez connaître la destination qui s'affiche actuellement (ou, en termes de navigation, quel est l'élément placé en haut de votre pile "Retour"), puis mettre à jour votre RallyTabRow à chaque fois que cet élément change.

Pour obtenir des informations en temps réel sur votre destination actuelle à partir de la pile "Retour", sous la forme d'un State, vous pouvez utiliser navController.currentBackStackEntryAsState(), puis récupérer sa destination:.

import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.runtime.getValue
// ...

@Composable
fun RallyApp() {
    RallyTheme {
        val navController = rememberNavController()

        val currentBackStack by navController.currentBackStackEntryAsState()
        // Fetch your currentDestination:
        val currentDestination = currentBackStack?.destination
        // ...
    }
}

currentBackStack?.destination renvoie NavDestination. Pour mettre à jour correctement le currentScreen, vous devez trouver un moyen de faire correspondre le retour de NavDestination avec l'un des trois composables d'écran principal de Rally. Vous devez déterminer la destination actuellement affichée pour pouvoir transmettre ces informations au RallyTabRow.. Comme nous l'avons dit, chaque destination est associée à un itinéraire unique, que nous pouvons utiliser comme une sorte d'ID de chaîne afin de trouver une correspondance unique.

Pour mettre à jour currentScreen, vous devez itérer la liste rallyTabRowScreens pour trouver un itinéraire correspondant, puis renvoyer le RallyDestination correspondant. Kotlin fournit une fonction .find() pratique pour cela :

import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.runtime.getValue
// ...

@Composable
fun RallyApp() {
    RallyTheme {
        val navController = rememberNavController()

        val currentBackStack by navController.currentBackStackEntryAsState()
        val currentDestination = currentBackStack?.destination

        // Change the variable to this and use Overview as a backup screen if this returns null
        val currentScreen = rallyTabRowScreens.find { it.route == currentDestination?.route } ?: Overview
        // ...
    }
}

Comme currentScreen est déjà transmis à RallyTabRow, vous pouvez exécuter l'application et vérifier que l'interface utilisateur de la barre d'onglets est mise à jour en conséquence.

6. Extraire des composables d'écran à partir de RallyDestinations

Jusqu'à présent, pour plus de simplicité, nous utilisions la propriété screen de l'interface RallyDestination et les objets d'écran basés sur cette propriété, pour ajouter l'UI composable dans NavHost (RallyActivity.kt :

import com.example.compose.rally.ui.overview.OverviewScreen
// ...

NavHost(
    navController = navController,
    startDestination = Overview.route,
    modifier = Modifier.padding(innerPadding)
) {
    composable(route = Overview.route) {
        Overview.screen()
    }
    // ...
}

Toutefois, les étapes suivantes de cet atelier (comme les événements de clic) nécessitent de transmettre directement des informations supplémentaires à vos écrans composables. Dans un environnement de production, les données à transmettre seront sans doute encore plus nombreuses.

La méthode la plus claire consiste à ajouter les composables directement dans le graphique de navigation NavHost et à les extraire de RallyDestination. Ensuite, RallyDestination et les objets d'écran ne contiendront que des informations de navigation, comme icon et route, et seraient dissociés de tout élément d'UI associé à Compose.

Ouvrez RallyDestinations.kt. Extrayez le composable de chaque écran à partir du paramètre screen des objets RallyDestination et dans les fonctions composable correspondantes dans NavHost, en remplaçant l'appel .screen() précédent, comme suit : :

import com.example.compose.rally.ui.accounts.AccountsScreen
import com.example.compose.rally.ui.bills.BillsScreen
import com.example.compose.rally.ui.overview.OverviewScreen
// ...

NavHost(
    navController = navController,
    startDestination = Overview.route,
    modifier = Modifier.padding(innerPadding)
) {
    composable(route = Overview.route) {
        OverviewScreen()
    }
    composable(route = Accounts.route) {
        AccountsScreen()
    }
    composable(route = Bills.route) {
        BillsScreen()
    }
}

À ce stade, vous pouvez supprimer sans crainte le paramètre screen de RallyDestination et de ses objets :

interface RallyDestination {
    val icon: ImageVector
    val route: String
}

/**
 * Rally app navigation destinations
 */
object Overview : RallyDestination {
    override val icon = Icons.Filled.PieChart
    override val route = "overview"
}
// ...

Exécutez à nouveau l'application et vérifiez que tout fonctionne comme avant. Maintenant que vous avez terminé cette étape, vous pouvez configurer des événements de clic dans vos écrans composables.

Activer les clics sur OverviewScreen

Actuellement, tous les événements de clic dans votre OverviewScreen sont ignorés. Les boutons "SEE ALL" (voir tout) de la sous-section "Accounts and Bills" (Comptes et factures) sont donc cliquables, mais ne mènent nulle part. L'objectif de cette étape est d'activer la navigation pour ces événements de clic.

Enregistrement de l'écran "Overview" (Aperçu), défilement vers les destinations de clic finales et tentative de clic. Les clics ne fonctionnent pas, car ils ne sont pas encore implémentés.

Le composable OverviewScreen peut accepter plusieurs fonctions de rappel définies en tant qu'événements de clic. Dans ce cas, il doit s'agir d'actions de navigation vous redirigeant vers AccountsScreen ou BillsScreen. Transmettez ces rappels de navigation à onClickSeeAllAccounts et onClickSeeAllBills pour accéder aux destinations souhaitées.

Ouvrez RallyActivity.kt, recherchez OverviewScreen dans NavHost et transmettez navController.navigateSingleTopTo(...) aux deux rappels de navigation avec les itinéraires correspondants :

OverviewScreen(
    onClickSeeAllAccounts = {
        navController.navigateSingleTopTo(Accounts.route)
    },
    onClickSeeAllBills = {
        navController.navigateSingleTopTo(Bills.route)
    }
)

Le navController aura désormais de suffisamment d'informations : il connaîtra notamment l'itinéraire de la destination exacte , qui permet d'accéder à la destination appropriée en un clic. Si vous examinez l'implémentation de OverviewScreen, vous constaterez que ces rappels sont déjà définis sur les paramètres onClick correspondants.:

@Composable
fun OverviewScreen(...) {
    // ...
    AccountsCard(
        onClickSeeAll = onClickSeeAllAccounts,
        onAccountClick = onAccountClick
    )
    // ...
    BillsCard(
        onClickSeeAll = onClickSeeAllBills
    )
}

Comme indiqué précédemment, conserver le navController au premier niveau de votre hiérarchie de navigation et le garder hissé au niveau de votre composable App (sans le transmettre directement à OverviewScreen), par exemple) vous permet de prévisualiser, de réutiliser et de tester le composable OverviewScreen facilement et de manière isolée, sans avoir besoin d'instances navController réelles ou simulées. Transmettre des rappels permet également d'apporter des modifications rapides aux événements de clic.

7. Utiliser les arguments pour accéder à SingleAccountScreen

Ajoutons de nouvelles fonctionnalités à nos écrans Accounts et Overview. À l'heure actuelle, ces écrans affichent une liste qui inclut différents types de comptes : "Compte courant", "Épargne", etc.

2f335ceab09e449a.png 2e78a5e090e3fccb.png

Toutefois, cliquer sur ces types de comptes n'a aucun effet (pour le moment). Résolvons à présent ce problème. Nous souhaitons afficher un nouvel écran présentant tous les détails du compte lorsque l'utilisateur appuie sur chaque type de compte. Nous devons donc fournir des informations supplémentaires à notre navController concernant le type de compte sur lequel clique l'utilisateur. Pour ce faire, utilisez des arguments.

Les arguments sont des outils très puissants pour rendre les itinéraires de navigation dynamiques en transmettant un ou plusieurs arguments sur un itinéraire. Elle permet d'afficher différentes informations en fonction des arguments fournis.

Dans RallyApp, incluez une nouvelle destination SingleAccountScreen (qui gérera l'affichage de ces comptes individuels) au graphique en ajoutant une nouvelle fonction composable au NavHost: existant.

import com.example.compose.rally.ui.accounts.SingleAccountScreen
// ...

NavHost(
    navController = navController,
    startDestination = Overview.route,
    modifier = Modifier.padding(innerPadding)
) {
    ...
    composable(route = SingleAccount.route) {
        SingleAccountScreen()
    }
}

Configurer la destination "SingleAccountScreen"

Lorsque vous arrivez sur la destination SingleAccountScreen, cette-ci a besoin d'informations supplémentaires pour savoir exactement quel type de compte elle doit afficher. Nous pouvons utiliser des arguments pour transmettre ce type d'informations. Vous devez spécifier que son itinéraire nécessite un argument {account_type}. Si vous examinez l'élément RallyDestination et son objet SingleAccount, vous remarquerez que cet argument a déjà été défini pour vous permettre de l'utiliser en tant que chaîne accountTypeArg.

Pour transmettre l'argument avec votre itinéraire lors de la navigation, vous devez les ajouter ensemble en suivant la logique suivante : "route/{argument}". Dans votre cas, cela se présente comme suit : "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}". N'oubliez pas que le signe $ est utilisé pour échapper les variables :

import androidx.navigation.NavType
import androidx.navigation.compose.navArgument
// ...

composable(
    route =
        "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}"
) {
    SingleAccountScreen()
}

Ainsi, lorsqu'une action est déclenchée pour naviguer vers SingleAccountScreen, un argument accountTypeArg doit également être transmis, sinon la navigation échouera. Considérez-la comme une signature ou un contrat qui doit être suivi par d'autres destinations qui souhaitent accéder à SingleAccountScreen.

La deuxième étape consiste à indiquer à composable qu'il doit accepter les arguments. Pour ce faire, définissez son paramètre arguments. Vous pouvez définir autant d'arguments que nécessaire, car la fonction composable accepte une liste d'arguments par défaut. Dans votre cas, il vous suffit d'ajouter un argument nommé accountTypeArg et de le renforcer en lui attribuant le niveau de sécurité String. Si vous ne définissez pas explicitement un type, il sera déduit de la valeur par défaut de cet argument :

import androidx.navigation.NavType
import androidx.navigation.compose.navArgument
// ...

composable(
    route =
        "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}",
    arguments = listOf(
        navArgument(SingleAccount.accountTypeArg) { type = NavType.StringType }
    )
) {
    SingleAccountScreen()
}

Ce code fonctionnerait et vous pourriez choisir de le conserver tel quel. Cependant, étant donné que toutes nos informations spécifiques à la destination se trouvent dans RallyDestinations.kt et ses objets, continuons à utiliser la même approche (comme nous l'avons fait ci-dessus pour Overview, Accounts, et Bills) et déplaçons la liste d'arguments dans SingleAccount:.

object SingleAccount : RallyDestination {
    // ...
    override val route = "single_account"
    const val accountTypeArg = "account_type"
    val arguments = listOf(
        navArgument(accountTypeArg) { type = NavType.StringType }
    )
}

Remplacez les arguments précédents par SingleAccount.arguments dans le composable composable correspondant de NavHost. Cela permet également de garantir la lisibilité de NavHost :

composable(
    route = "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}",
    arguments =  SingleAccount.arguments
) {
    SingleAccountScreen()
}

Maintenant que vous avez défini votre itinéraire complet avec des arguments pour SingleAccountScreen, l'étape suivante consiste à vous assurer que cet accountTypeArg est transmis plus en détail au composable SingleAccountScreen, afin qu'il sache quel type de compte afficher correctement. Si vous examinez l'implémentation de SingleAccountScreen, vous constaterez qu'il est déjà configuré et attend d'accepter un paramètre accountType :

fun SingleAccountScreen(
    accountType: String? = UserData.accounts.first().name
) {
   // ...
}

Pour résumer :

  • Vous vous êtes assuré de définir l'itinéraire de demande d'argument en tant que signal vers ses destinations précédentes.
  • Vous vous êtes assuré que le composable sait qu'il doit accepter les arguments.

La dernière étape consiste à récupérer l'argument transmis.

Dans Compose Navigation, chaque fonction composable NavHost a accès à la NavBackStackEntry actuelle, une classe qui contient les informations sur l'itinéraire actuel et les arguments transmis d'une entrée dans la pile "Retour". Vous pouvez l'utiliser pour obtenir la liste d'arguments requise à partir de navBackStackEntry, puis rechercher et récupérer l'argument exact dont vous avez besoin, pour ensuite le transmettre à votre écran composable.

Dans ce cas, vous demanderez accountTypeArg à navBackStackEntry. Vous devez ensuite le transmettre au paramètre accountType de SingleAccountScreen'.

Vous pouvez également indiquer une valeur par défaut pour l'argument, en tant qu'espace réservé, au cas où celui-ci n'aurait pas été fourni. Couvrir ce cas particulier vous permet de renforcer la sécurité de votre code.

Le code devrait se présenter ainsi :

NavHost(...) {
    // ...
    composable(
        route =
          "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}",
        arguments = SingleAccount.arguments
    ) { navBackStackEntry ->
        // Retrieve the passed argument
        val accountType =
            navBackStackEntry.arguments?.getString(SingleAccount.accountTypeArg)

        // Pass accountType to SingleAccountScreen
        SingleAccountScreen(accountType)
    }
}

Votre SingleAccountScreen dispose désormais des informations nécessaires pour afficher le type de compte approprié lorsque vous naviguez vers celui-ci. Si vous examinez l'implémentation de SingleAccountScreen,, vous pouvez voir qu'il fait déjà correspondre l'accountType transmis à la source UserData pour récupérer les détails du compte correspondants.

Reproduisons une tâche d'optimisation mineure : déplacez l'itinéraire "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}" vers RallyDestinations.kt et son objet SingleAccount:.

object SingleAccount : RallyDestination {
    // ...
    override val route = "single_account"
    const val accountTypeArg = "account_type"
    val routeWithArgs = "${route}/{${accountTypeArg}}"
    val arguments = listOf(
        navArgument(accountTypeArg) { type = NavType.StringType }
    )
}

Remplacez-le à nouveau dans le NavHost composable: correspondant :

// ...
composable(
    route = SingleAccount.routeWithArgs,
    arguments = SingleAccount.arguments
) {...}

Configurer les destinations de départ "Comptes" et "Aperçu"

Maintenant que vous avez défini votre itinéraire SingleAccountScreen ainsi que l'argument requis et accepté pour effectuer correctement la navigation vers SingleAccountScreen, vous devez vous assurer que le même argument accountTypeArg est transmis depuis la destination précédente, quelle que soit la destination.

Comme vous pouvez le voir, vous devez tenir compte de ces deux aspects : la destination de départ qui fournit et transmet un argument, et la destination de destination qui accepte cet argument et l'utilise pour afficher les bonnes informations. Ces deux destinations doivent être définies explicitement.

Par exemple, lorsque vous vous trouvez sur la destination Accounts et appuyez sur le type de compte "Checking" (Compte courant), la destination "Accounts" (Comptes) doit transmettre une chaîne "Checking" en tant qu'argument, ajoutée à l'itinéraire de chaîne "Single_account" pour ouvrir le SingleAccountScreen correspondant. Son chemin de chaîne ressemblerait à ceci : "single_account/Checking"

Vous devez utiliser exactement la même route avec l'argument transmis lorsque vous utilisez navController.navigateSingleTopTo(...),, comme suit :

navController.navigateSingleTopTo("${SingleAccount.route}/$accountType").

Transmettez ce rappel d'action de navigation au paramètre onAccountClick de OverviewScreen et AccountsScreen. Notez que ces paramètres sont prédéfinis en tant que onAccountClick: (String) -> Unit, avec "String" en entrée. Ainsi, lorsque l'utilisateur appuie sur un type de compte spécifique dans Overview et Account, la chaîne accountType est déjà disponible et peut être transmise facilement en tant qu'argument nav :

OverviewScreen(
    // ...
    onAccountClick = { accountType ->
        navController
          .navigateSingleTopTo("${SingleAccount.route}/$accountType")
    }
)
// ...

AccountsScreen(
    // ...
    onAccountClick = { accountType ->
        navController
          .navigateSingleTopTo("${SingleAccount.route}/$accountType")
    }
)

Pour faciliter la lecture, vous pouvez extraire cette action de navigation dans une fonction d'extension d'assistance privée :

import androidx.navigation.NavHostController
// ...
OverviewScreen(
    // ...
    onAccountClick = { accountType ->
        navController.navigateToSingleAccount(accountType)
    }
)

// ...

AccountsScreen(
    // ...
    onAccountClick = { accountType ->
        navController.navigateToSingleAccount(accountType)
    }
)

// ...

private fun NavHostController.navigateToSingleAccount(accountType: String) {
    this.navigateSingleTopTo("${SingleAccount.route}/$accountType")
}

À ce stade, lorsque vous exécutez l'application, vous pouvez cliquer sur chaque type de compte et accéder au SingleAccountScreen correspondant, qui affiche les données du compte donné.

Enregistrement de l'écran "Overview" (Aperçu), défilement vers les destinations de clic finales et tentative de clic. Les clics mènent à présent à des destinations.

8. Activer les liens profonds

En plus d'ajouter des arguments, vous pouvez ajouter des liens profonds pour associer une URL, une action et/ou un type MIME spécifiques à un composable. Dans Android, un lien profond est un lien qui vous redirige directement vers une destination spécifique d'une application. Navigation Compose est compatible avec les liens profonds implicites. Lorsque le lien profond implicite est appelé (par exemple, lorsqu'un utilisateur clique dessus), Android peut ouvrir votre application à la destination correspondante.

Dans cette section, vous allez ajouter un nouveau lien profond permettant de naviguer vers le composable SingleAccountScreen avec un type de compte correspondant, et activer ce lien profond pour qu'il soit également accessible aux applications externes. Rappelez-vous : l'itinéraire de ce composable était "single_account/{account_type}", et c'est celui que vous allez utiliser pour le lien profond, en apportant quelques modifications mineures propres à ce type de lien.

L'exposition des liens profonds aux applications externes n'est pas activée par défaut. Vous devez donc également ajouter des éléments <intent-filter> au fichier manifest.xml de votre application. Ce sera donc la première étape.

Commencez par ajouter le lien profond au AndroidManifest.xml de l'application. Vous devez créer un filtre d'intent via <intent-filter> dans <activity>, avec l'action VIEW et les catégories BROWSABLE et DEFAULT.

Dans le filtre, vous devez ensuite ajouter la balise data pour ajouter un scheme (rally : nom de votre application) et host. (single_account : itinéraire vers votre composable) pour définir votre lien profond. Vous obtenez rally://single_account, qui est l'URL du lien profond.

Notez qu'il n'est pas nécessaire de déclarer l'argument account_type dans AndroidManifest. Cette valeur sera ajoutée plus tard dans la fonction composable NavHost.

<activity
    android:name=".RallyActivity"
    android:windowSoftInputMode="adjustResize"
    android:label="@string/app_name"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="rally" android:host="single_account" />
    </intent-filter>
</activity>

Vous pouvez à présent réagir aux intents entrants depuis RallyActivity.

Le composable SingleAccountScreen accepte déjà les arguments, mais il doit maintenant accepter le lien profond nouvellement créé pour lancer cette destination lorsque son lien profond est déclenché.

Dans la fonction composable de SingleAccountScreen, ajoutez un autre paramètre deepLinks. Comme pour arguments,, il accepte également une liste de navDeepLink, car vous pouvez définir plusieurs liens profonds menant à la même destination. Transmettez l'élément uriPattern (qui correspond à celui défini dans intent-filter dans votre fichier manifeste, rally://singleaccount). Cette fois, vous ajouterez également son argument accountTypeArg :

import androidx.navigation.navDeepLink
// ...

composable(
    route = SingleAccount.routeWithArgs,
    // ...
    deepLinks = listOf(navDeepLink {
        uriPattern = "rally://${SingleAccount.route}/{${SingleAccount.accountTypeArg}}"
    })
)

Vous devinez la suite. Déplacez cette liste dans RallyDestinations SingleAccount:.

object SingleAccount : RallyDestination {
    // ...
    val arguments = listOf(
        navArgument(accountTypeArg) { type = NavType.StringType }
    )
    val deepLinks = listOf(
       navDeepLink { uriPattern = "rally://$route/{$accountTypeArg}"}
    )
}

Et une nouvelle fois, remplacez-le dans le composable NavHost correspondant :

// ...
composable(
    route = SingleAccount.routeWithArgs,
    arguments = SingleAccount.arguments,
    deepLinks = SingleAccount.deepLinks
) {...}

Votre application et SingleAccountScreen peuvent maintenant prendre en charge les liens profonds. Pour tester leur bon fonctionnement, installez une nouvelle fois l'application Rally sur un émulateur ou un appareil connecté, ouvrez une ligne de commande et exécutez la commande suivante pour simuler le lancement d'un lien profond :

adb shell am start -d "rally://single_account/Checking" -a android.intent.action.VIEW

Vous accédez directement au compte courant. Vous pouvez vérifier que cela fonctionne pour tous les types de compte dans l'application.

9. Extraire NavHost dans RallyNavHost

Votre NavHost est à présent terminé. Toutefois, pour tester son fonctionnement et épurer votre RallyActivity, vous pouvez extraire votre NavHost actuel et ses fonctions d'assistance, par exemple extraire navigateToSingleAccount du composable RallyApp vers sa propre fonction composable, et le renommer RallyNavHost.

RallyApp est le seul et unique composable avec lequel vous devez utiliser directement navController. Comme indiqué précédemment, chaque autre écran composable imbriqué doit obtenir uniquement des rappels de navigation, et non le navController lui-même.

Par conséquent, le nouveau RallyNavHost accepte les navController et modifier comme paramètres de RallyApp :

@Composable
fun RallyNavHost(
    navController: NavHostController,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = Overview.route,
        modifier = modifier
    ) {
        composable(route = Overview.route) {
            OverviewScreen(
                onClickSeeAllAccounts = {
                    navController.navigateSingleTopTo(Accounts.route)
                },
                onClickSeeAllBills = {
                    navController.navigateSingleTopTo(Bills.route)
                },
                onAccountClick = { accountType ->
                   navController.navigateToSingleAccount(accountType)
                }
            )
        }
        composable(route = Accounts.route) {
            AccountsScreen(
                onAccountClick = { accountType ->
                   navController.navigateToSingleAccount(accountType)
                }
            )
        }
        composable(route = Bills.route) {
            BillsScreen()
        }
        composable(
            route = SingleAccount.routeWithArgs,
            arguments = SingleAccount.arguments,
            deepLinks = SingleAccount.deepLinks
        ) { navBackStackEntry ->
            val accountType =
              navBackStackEntry.arguments?.getString(SingleAccount.accountTypeArg)
            SingleAccountScreen(accountType)
        }
    }
}

fun NavHostController.navigateSingleTopTo(route: String) =
    this.navigate(route) { launchSingleTop = true }

private fun NavHostController.navigateToSingleAccount(accountType: String) {
    this.navigateSingleTopTo("${SingleAccount.route}/$accountType")
}

Ajoutez maintenant le nouveau RallyNavHost à votre RallyApp et exécutez à nouveau l'application pour vous assurer que le fonctionnement de votre code n'est pas altéré :

fun RallyApp() {
    RallyTheme {
    ...
        Scaffold(
        ...
        ) { innerPadding ->
            RallyNavHost(
                navController = navController,
                modifier = Modifier.padding(innerPadding)
            )
        }
     }
}

10. Tester la navigation dans Compose

Au début de cet atelier de programmation, vous avez veillé à ne pas transmettre le navController directement à des composables (autres qu'à l'application de niveau élevé), mais à transmettre des rappels de navigation en tant que paramètres. Tous vos composables peuvent ainsi être testés individuellement, car ils ne nécessitent pas d'instance de navController lors des tests.

Vous devez toujours vérifier que l'ensemble du mécanisme de navigation de Compose fonctionne comme prévu dans votre application, en testant RallyNavHost et les actions de navigation transmises à vos composables. Ce sont les principaux objectifs de cette section. Pour tester chaque fonction composable séparément, suivez l'atelier de programmation Tests dans Jetpack Compose.

Pour commencer les tests, nous devons d'abord ajouter les dépendances de test requises. Revenez au fichier de compilation de votre application, accessible via app/build.gradle. Dans la section des dépendances de test, ajoutez la dépendance navigation-testing.

dependencies {
// ...
  androidTestImplementation "androidx.navigation:navigation-testing:$rootProject.composeNavigationVersion"
  // ...
}

Préparer la classe NavigationTest

Votre RallyNavHost peut être testé indépendamment de l'Activity elle-même.

Comme ce test s'exécute toujours sur un appareil Android, vous devez créer le répertoire de test /app/src/androidTest/java/com/example/compose/rally, puis créer une classe de test pour le fichier de test et la nommer NavigationTest.

Pour commencer, afin d'utiliser les API de test Compose, et de tester et contrôler les composables et les applications à l'aide de Compose, ajoutez une règle de test Compose :

import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()

}

Écrire votre premier test

Créez une fonction de test rallyNavHost publique et annotez-la avec @Test. Dans cette fonction, vous devez d'abord définir le contenu Compose que vous souhaitez tester. Pour cela, utilisez le setContent de composeTestRule. Il utilise un paramètre composable en tant que corps, et vous permet d'écrire du code Compose et d'ajouter des composables dans un environnement de test, comme si vous vous trouviez dans une application d'environnement de production standard.

Dans setContent,, vous pouvez configurer l'objet de votre test actuel, RallyNavHost, et lui transmettre une instance d'une nouvelle instance navController. L'artefact de test de navigation fournit un TestNavHostController très pratique. Ajoutons donc cette étape :

import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.ComposeNavigator
import androidx.navigation.testing.TestNavHostController
import org.junit.Assert.fail
import org.junit.Test
// ...

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    lateinit var navController: TestNavHostController

    @Test
    fun rallyNavHost() {
        composeTestRule.setContent {
            // Creates a TestNavHostController
            navController =
                TestNavHostController(LocalContext.current)
            // Sets a ComposeNavigator to the navController so it can navigate through composables
            navController.navigatorProvider.addNavigator(
                ComposeNavigator()
            )
            RallyNavHost(navController = navController)
        }
        fail()
    }
}

Si vous avez copié le code ci-dessus, l'appel fail() fait échouer votre test jusqu'à ce qu'une assertion réelle soit établie, ceci afin de vous rappeler de terminer le test.

Pour vérifier que le composable d'écran approprié s'affiche, vous pouvez utiliser son contentDescription et affirmer qu'il est affiché. Dans cet atelier de programmation, vous avez déjà défini des contentDescription pour les comptes et les destinations "Overview" (Aperçu). Vous pouvez donc les utiliser pour les vérifications de test.

Lors de la première vérification, vous devez vérifier que l'écran "Overview" (Aperçu) s'affiche en tant que première destination lorsque RallyNavHost est initialisé pour la première fois. Vous devez également renommer le test en conséquence : appelez-le rallyNavHost_verifyOverviewStartDestination. Pour ce faire, remplacez l'appel fail() par le code suivant :

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithContentDescription
// ...

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    lateinit var navController: TestNavHostController

    @Test
    fun rallyNavHost_verifyOverviewStartDestination() {
        composeTestRule.setContent {
            navController =
                TestNavHostController(LocalContext.current)
            navController.navigatorProvider.addNavigator(
                ComposeNavigator()
            )
            RallyNavHost(navController = navController)
        }

        composeTestRule
            .onNodeWithContentDescription("Overview Screen")
            .assertIsDisplayed()
    }
}

Exécutez à nouveau le test et vérifiez qu'il réussit.

Puisque vous devez configurer RallyNavHost de la même manière pour chacun des tests à venir, vous pouvez extraire son initialisation dans une fonction @Before annotée pour éviter les répétitions inutiles et rendre vos tests plus concis :

import org.junit.Before
// ...

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: TestNavHostController

    @Before
    fun setupRallyNavHost() {
        composeTestRule.setContent {
            navController =
                TestNavHostController(LocalContext.current)
            navController.navigatorProvider.addNavigator(
                ComposeNavigator()
            )
            RallyNavHost(navController = navController)
        }
    }

    @Test
    fun rallyNavHost_verifyOverviewStartDestination() {
        composeTestRule
            .onNodeWithContentDescription("Overview Screen")
            .assertIsDisplayed()
    }
}

Vous pouvez tester l'implémentation de la navigation de différentes manières : en cliquant sur les éléments de l'UI, puis en vérifiant la destination affichée ou en comparant l'itinéraire attendu par rapport à l'itinéraire actuel.

Tests via des clics dans l'interface utilisateur et attribut contentDescription de l'écran

Pour tester l'implémentation de votre application, il est préférable de tester les clics sur les éléments de l'UI. Pour vérifier que le message suivant s'affiche bien dans l'écran "Overview" (Aperçu), cliquez sur "SEE ALL" (Tout afficher) dans la sous-section "Accounts" (Comptes) pour accéder à la destination "Accounts" :

5a9e82acf7efdd5b.png

Vous allez à nouveau utiliser le contentDescription défini sur ce bouton spécifique dans le composable OverviewScreenCard,. Pour ce faire, simulez un clic sur performClick() et vérifiez que la destination "Accounts" (Comptes) s'affiche ensuite :

import androidx.compose.ui.test.performClick
// ...

@Test
fun rallyNavHost_clickAllAccount_navigatesToAccounts() {
    composeTestRule
        .onNodeWithContentDescription("All Accounts")
        .performClick()

    composeTestRule
        .onNodeWithContentDescription("Accounts Screen")
        .assertIsDisplayed()
}

Vous pouvez suivre cette logique pour tester toutes les actions de navigation restantes dans l'application.

Tests via la comparaison des itinéraires et des clics dans l'interface utilisateur

Vous pouvez également utiliser navController pour vérifier vos assertions en comparant les routes de chaîne actuelles à celles attendues. Pour ce faire, cliquez sur l'interface utilisateur (comme dans la section précédente), puis comparez l'itinéraire actuel à celui attendu en utilisant navController.currentBackStackEntry?.destination?.route.

Vous devez également faire défiler la page jusqu'à la sous-section "Bills" (Factures) de l'écran "Overview" (Aperçu). Sinon, le test échouera, car il ne pourra pas trouver de nœud avec contentDescription "All Bills" (Toutes les factures) :

import androidx.compose.ui.test.performScrollTo
import org.junit.Assert.assertEquals
// ...

@Test
fun rallyNavHost_clickAllBills_navigateToBills() {
    composeTestRule.onNodeWithContentDescription("All Bills")
        .performScrollTo()
        .performClick()

    val route = navController.currentBackStackEntry?.destination?.route
    assertEquals(route, "bills")
}

En suivant ces logiques, vous pouvez terminer votre classe de test en couvrant les autres itinéraires de navigation, destinations et actions de clics. Exécutez maintenant l'ensemble des tests pour vérifier qu'ils réussissent tous.

11. Félicitations

Bravo ! Vous êtes arrivé au terme de cet atelier de programmation. Vous pouvez consulter le code de solution et le comparer au vôtre.

Vous avez ajouté la navigation avec Jetpack Compose à l'application Rally et vous comprenez maintenant ses concepts clés. Vous avez appris à configurer un graphique de navigation comportant des destinations des composables, à définir des actions et des itinéraires de navigation, à transmettre des informations supplémentaires aux itinéraires via des arguments, à configurer des liens profonds et à tester votre navigation.

Pour consulter des articles sur le sujet et en savoir plus sur l'intégration de la barre de navigation inférieure, la navigation pour les projets multimodules, les graphiques imbriquéset bien plus, consultez le dépôt GitHub Now in Android et découvrir leur implémentation.

Et maintenant ?

Consultez ces ressources pour continuer votre parcours d'apprentissage Jetpack Compose :

En savoir plus sur la Navigation dans Jetpack :

Documents de référence