1. Introduction
Dernière mise à jour : 25/07/2022
Ce dont vous avez besoin
- Dernière version d'Android Studio
- Connaissances en Kotlin et des lambdas de fin
- Connaissances de base sur la navigation et ses termes, comme la pile "Retour"
- Connaissances de base sur Compose
- Il peut être utile de suivre l'atelier de programmation sur les principes de base de Jetpack Compose avant cet atelier de programmation.
- Connaissances de base sur la gestion de l'état dans Compose
- Pensez à suivre l'atelier intitulé L'état dans Jetpack Compose avant cet atelier.
Naviguer avec Compose
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 :
OverviewScreen
: présentation de toutes les alertes et transactions financièresAccountsScreen
: informations sur les comptes existantsBillsScreen
: dépenses planifiées
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 :
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 :
- Un élément
icon
pour la barre supérieure - Une chaîne
route
(requise pour la navigation dans Compose, et qui sera le chemin menant à cette destination) - 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 :
- Ajouter la dernière dépendance Compose Navigation
- Configurer le
NavController
- Ajouter un
NavHost
et créer le graphique de navigation - Préparer les itinéraires pour naviguer entre les différentes destinations de l'application
- 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 :
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 :
- Lorsque vous appuyez plusieurs fois sur le même onglet, plusieurs copies de la même destination se lancent.
- 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 :
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 parPopUpToBuilder.saveState
ou par l'attributpopUpToSaveState
. 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.
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.
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é.
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>
Déclencher et valider le lien profond
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
) {...}
Tester le lien profond à l'aide d'adb
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()
}
}
Tester la navigation
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" :
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 :
- Atelier de programmation intitulé "Tests dans Compose"
- Vidéo "Problèmes de performances courants dans Jetpack Compose"
En savoir plus sur la Navigation dans Jetpack :