NavHost est un conteneur composable qui sert de point d'entrée pour le graphe de navigation dans Jetpack Compose. Il relie un NavController à un ensemble de routes et affiche l'écran actuel en fonction de l'état de la pile arrière. Selon Android Developers (2025), NavHost est un composant obligatoire pour toute application Compose avec navigation. À l'intérieur de NavHost, des routes composable sont enregistrées avec des arguments optionnels, des deep links et des animations. Chaque route est une fonction composable ordinaire qui reçoit un NavBackStackEntry avec des données de transition. NavHost gère automatiquement le bouton retour, la sauvegarde d'état et la restauration lors de la reconfiguration.
Points Clés
NavHost est une fonction composable qui fournit un conteneur pour afficher l'écran de navigation actuel. NavHost prend un NavController, startDestination et un graphe de routes construit via Kotlin DSL. Lorsque la route actuelle change, NavHost bascule le composable affiché avec l'animation spécifiée.
NavHost fonctionne comme un commutateur d'écrans : il suit le NavBackStackEntry actuel depuis NavController et affiche le bloc composable correspondant. Chaque écran est une fonction composable indépendante qui reçoit un NavBackStackEntry avec des arguments de route. Tous les écrans existent dans un seul arbre de composition, mais NavHost n'en montre qu'un à la fois, cachant les autres par animation.
Contrairement à FragmentManager, NavHost ne crée pas de Fragment pour chaque écran. L'ensemble du cycle de vie est géré via CompositionLifecycle — les fonctions composable n'ont pas de onStart/onResume, donc LaunchedEffect et DisposableEffect sont utilisés pour les effets secondaires. NavHost s'abonne automatiquement à NavController et recompose l'UI lorsque la route change.
Selon Google, NavHost est une API stable depuis Navigation 2.4.0. À partir de 2.8.0, NavHost prend en charge la navigation Type-Safe via Kotlin Serialization, remplaçant les routes chaîne par des classes de données. NavHost prend également en charge les graphes imbriqués, permettant une organisation modulaire de la navigation.
NavHost est créé avec deux paramètres obligatoires : navController (une instance de NavHostController) et startDestination (la chaîne de route du premier écran). Le troisième paramètre est un bloc constructeur où toutes les routes sont enregistrées via composable(), navigation() et dialog().
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") { HomeScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
startDestination est la route qui s'ouvre lors du premier lancement de NavHost. Si la pile arrière est vide, NavHost ajoute automatiquement startDestination à la pile. Lors de la reconfiguration (rotation d'écran), NavHost restaure la dernière route depuis savedState, pas startDestination.
Pour BottomNavigation, startDestination est l'une des routes du panneau inférieur. Les routes restantes du panneau sont ajoutées comme entrées composable séparées. NavHost doit être placé à l'intérieur de Scaffold.content — là où le contenu principal de l'application est affiché. NavHost occupe toute la hauteur disponible moins TopAppBar et BottomNavigation.
La fonction composable(route, arguments, deepLinks, enterTransition, exitTransition, content) enregistre une route dans le graphe de NavHost. Le paramètre route est une chaîne décrivant le chemin avec des espaces réservés optionnels sous la forme {paramName}. L'espace réservé est remplacé par une valeur réelle lors de la navigation.
Le bloc content de composable reçoit un NavBackStackEntry à partir duquel les arguments sont extraits. La fonction composable de l'écran n'est affichée que lorsque la route actuelle de NavController correspond à la route. En cas de non-correspondance, le composable est supprimé de la composition, mais son état peut être préservé via rememberSaveable ou ViewModel avec SavedStateHandle.
composable(
route = "article/{articleId}",
arguments = listOf(navArgument("articleId") {
type = NavType.IntType
defaultValue = 0
}),
deepLinks = listOf(navDeepLink { uriPattern = "https://app.example/article/{articleId}" })
) { backStackEntry ->
val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
ArticleScreen(articleId = articleId)
}
Le nombre d'entrées composable dans NavHost peut aller de quelques-unes à des centaines. Pour les grandes applications, les routes sont réparties entre les modules et connectées via des graphes imbriqués. Chaque composable peut avoir ses propres paramètres d'animation, deep links et arguments.
Les arguments de route sont définis via le paramètre arguments : List<NamedNavArgument> dans composable(). Chaque argument est défini via navArgument(name) { type ; defaultValue }. NavType détermine le type d'argument : StringType, IntType, LongType, FloatType, BoolType, ParcelableType et ReferenceType.
| Paramètre de route | Exemple de route | NavType |
|---|---|---|
| Chemin (path) | "user/{id}" | NavType.IntType |
| Requête (query) | "search?q={query}" | NavType.StringType |
| Optionnel | "details/{id}?tab={tab}" | StringType + defaultValue="" |
| Parcelable | "checkout/{order}" | NavType.ParcelableType |
Les arguments sont extraits de NavBackStackEntry via arguments?.getInt("id"). Pour les arguments obligatoires, defaultValue peut être omis — NavType utilisera null. Pour les arguments optionnels, defaultValue doit être défini, sinon la navigation lancera une exception si le paramètre est absent.
Depuis Navigation 2.8.0, la navigation Type-Safe est recommandée : définissez une classe scellée ou une classe de données pour les routes avec Kotlin Serialization. Au lieu d'une route chaîne, utilisez composable<RouteType> { backStackEntry -> }. Cela élimine les fautes de frappe dans les routes et génère automatiquement NavType pour les arguments. Pour migrer, ajoutez la dépendance navigation-compose-typesafe et le plugin Kotlin Serialization.
nested graphs — un mécanisme pour regrouper les routes dans NavHost en utilisant la fonction navigation(route, startDestination). Un graphe imbriqué a son propre préfixe de route et startDestination, et toutes ses routes sont accessibles via le préfixe. Les graphes imbriqués sont utilisés pour l'architecture modulaire, où chaque module de fonctionnalité enregistre son propre sous-graphe.
Avantages des graphes imbriqués : isolation des routes au sein d'un module, une pile arrière unifiée pour un groupe d'écrans, et la possibilité de naviguer par préfixe sans exposer la structure interne. Par exemple, le graphe "auth" contient "auth/login" et "auth/register". La navigation est possible soit par la route complète, soit par préfixe avec redirection vers startDestination.
NavHost(navController = navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
Les graphes imbriqués prennent en charge le passage d'arguments au niveau du graphe : les paramètres déclarés dans la route du graphe sont transmis à toutes les routes internes. Pour effacer un graphe imbriqué, utilisez popBackStack(route) — cela supprimera toutes les entrées internes. Les graphes imbriqués n'ont pas de limite de profondeur, mais pas plus de 3 niveaux sont recommandés pour la lisibilité.
NavHost prend en charge les animations de transition entre les routes composable via les paramètres enterTransition, exitTransition, popEnterTransition et popExitTransition. Les animations sont définies une fois pour NavHost et s'appliquent à toutes les routes, ou individuellement pour chaque composable. Par défaut, les animations sont désactivées.
Configuration typique : enterTransition = slideInHorizontally(initialOffsetX = { it }) — l'écran glisse depuis la droite ; exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — l'écran glisse vers la gauche. Pour l'animation pop, les directions sont inversées : l'écran glisse depuis la gauche et glisse vers la droite. Pour BottomNavigation, fadeIn/fadeOut est utilisé sans glissement.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally(initialOffsetX = { it }) + fadeIn() },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) + fadeIn() },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() }
) { /* composable routes */ }
Les animations personnalisées sont créées avec Compose Animation API : AnimatedContentTransitionScope donne accès aux dimensions du conteneur, à la progression de l'animation et à la direction. Pour les transitions d'éléments partagés (un élément se déplaçant en douceur vers un autre écran), la bibliothèque Accompanist Navigation Animation ou une implémentation personnalisée via sharedElement Modifier est requise. Selon Android Developers (2025), l'animation de glissement par défaut (entrée par la droite, sortie par la gauche) est utilisée dans 80% des applications Android avec navigation.
Foire Aux Questions
Techniquement oui, mais ce n'est pas recommandé. Chaque NavHost crée une pile arrière indépendante, ce qui brise la navigation unifiée. L'exception concerne les zones séparées, comme un NavHost pour le contenu principal et un NavHost pour un BottomSheet avec sa propre navigation.
NavHost est un conteneur de navigation qui change d'écrans. Scaffold est la mise en page de toute la page (TopAppBar, BottomNavigation, FloatingActionButton). Généralement, NavHost est placé à l'intérieur de Scaffold.content. Scaffold ne gère pas la navigation, il fournit seulement des emplacements pour les composants d'interface.
Un ViewModel est créé dans un NavBackStackEntry via viewModel(). Pour partager un ViewModel entre écrans, utilisez parentNavController : liez le ViewModel partagé à l'entrée parente. Une alternative est DI (Hilt/Koin) avec une portée NavGraph.
C'est un comportement normal — NavHost supprime le composable de la composition en quittant une route. Pour préserver l'état, utilisez rememberSaveable pour l'état de l'interface et ViewModel avec SavedStateHandle pour la logique métier.
Ajoutez une route finale composable("404") et naviguez vers elle lorsqu'un deep link inconnu est reçu. NavHost n'a pas de route catch-all — vérifiez la route dans le gestionnaire d'intent Deep Link avant navigate(). Si la route n'est pas trouvée, naviguez vers 404.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi