composable() : ce que c'est, NavHost et routage dans Jetpack Compose

Auteur : IT Sectr Publié le : 2026-06-30 Temps de lecture : 9 min

composable() est une fonction de la bibliothèque Navigation Compose qui enregistre un écran dans NavHost et relie une route URL à la mise en page Compose. Lorsque la navigation se dirige vers une route définie, Jetpack Compose appelle la fonction composable correspondante et l'affiche comme écran actuel. Contrairement à FragmentManager ou à la navigation basée sur Intent, composable() fonctionne au niveau d'une seule Activity et est entièrement géré via Kotlin DSL. Selon Android Developers (2025), plus de 73% des applications Android modernes construites avec Jetpack Compose utilisent Navigation Compose pour organiser les transitions d'écran.

Points clés

  • composable() est une fonction d'enregistrement d'écran dans NavHost de la bibliothèque Navigation Compose.
  • Route — chaque écran est identifié par une route chaîne passée comme premier argument.
  • Paramètres — composable() prend en charge les arguments via NavArgument, y compris obligatoires et optionnels.
  • Imbrication — la navigation imbriquée est prise en charge via des NavHost imbriqués avec des graphes de routes séparés.
  • Performance — composable() utilise l'initialisation paresseuse : l'écran n'est créé qu'à la première navigation.

Qu'est-ce que composable() dans NavHost

composable() est une fonction d'extension de l'objet NavHost. Kotlin DSL permet de l'appeler à l'intérieur du bloc NavHost pour décrire déclarativement tous les écrans de l'application. Chaque appel crée une entrée dans le graphe de navigation, liant une route chaîne à une fonction composable. Lorsqu'un utilisateur navigue vers une route spécifique, NavHost affiche le composable correspondant comme écran actuel, masquant le précédent.

La bibliothèque Navigation Compose a été présentée par Google en 2021 comme une alternative à la navigation basée sur Fragment pour Jetpack Compose. Le principal avantage est la compatibilité totale avec le paradigme Compose : composable() fonctionne dans le même cycle de vie que les autres composants Compose, sans nécessiter FragmentManager ou transactions. Cela élimine une classe de bugs liés à l'inadéquation des cycles de vie Fragment et Compose.

Chaque composable() prend une route chaîne et une fonction lambda qui reçoit un objet NavBackStackEntry et retourne une UI Composable. À l'intérieur de la lambda, on peut accéder à NavController via navController depuis la portée, permettant la navigation vers d'autres écrans. Cette architecture rend la navigation explicite et prévisible.

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

Comment fonctionne composable() : clés et paramètres

Chaque appel à composable() crée un sommet avec un identifiant de route unique dans le graphe interne de NavHost. Lorsque NavController exécute navigate(), la bibliothèque compare la route demandée avec tous les sommets composable enregistrés et trouve une correspondance. Après la correspondance, un NavBackStackEntry est créé, placé sur la pile de navigation, et la composition de l'UI commence.

L'implémentation interne de composable() utilise un mécanisme d'initialisation paresseuse : la composition de l'écran ne se produit qu'à la première navigation vers cette route. Cela signifie que les écrans vers lesquels l'utilisateur n'a jamais navigué n'occupent pas de mémoire et n'exécutent aucun code. Cette approche améliore considérablement les performances dans les applications avec de nombreux écrans.

Le paramètre key dans composable() permet de gérer la recréation de l'écran. Par défaut, composable n'est pas recréé lors d'une navigation répétée vers la même route — NavHost utilise l'entrée existante de la pile arrière. Cependant, si une key est passée et qu'elle change, NavHost créera une nouvelle instance de la fonction composable. Ceci est utile pour les écrans avec des données dynamiques où il faut forcer l'actualisation de l'état lors de la réouverture.

kotlin
val NavGraphBuilder.Composable: Unit
    get() = composable(
        route = "details/{itemId}",
        arguments = listOf(
            NavArgument("itemId") { 
                type = NavType.IntType
            }
        ),
        deepLinks = listOf(
            navDeepLink { uriPattern = "myapp://details/{itemId}" }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailsScreen(itemId = itemId)
    }

Passer des arguments via composable()

composable() prend en charge un système d'arguments flexible via le paramètre arguments. Chaque argument est décrit par un objet NavArgument qui définit le type, la valeur par défaut et l'obligation. Les arguments sont passés dans la route comme paramètres de chemin (via des accolades) ou paramètres de requête (via un point d'interrogation).

Les paramètres de chemin sont spécifiés directement dans le modèle de route : "profile/{userId}". Lors de la navigation vers "profile/42", NavHost extrait automatiquement la valeur 42 et la rend accessible via backStackEntry.arguments. Les paramètres de requête sont ajoutés après le point d'interrogation : "search?query={text}" et sont également analysés automatiquement par la bibliothèque.

Lors de l'extraction des arguments, il est important de vérifier l'obligation du paramètre via NavType.isNullableAllowed et de fournir des valeurs par défaut via NavArgument defaultValue. Si un paramètre obligatoire est manquant, Navigation Compose lance une IllegalArgumentException, empêchant les bugs subtils avec des routes incorrectes.

Type d'argumentNavTypeExemple dans route
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.LongType"article/{timestamp}"

Pour passer des objets complexes, il est recommandé d'utiliser NavType.ParcelableType ou NavType.SerializableType. Cependant, Google conseille de minimiser la taille des données transférées — il est préférable de passer un identifiant et de charger l'objet par ID dans l'écran. Cela évite les problèmes avec les grandes données sérialisées et simplifie le traitement des changements de configuration.

kotlin
data class Profile(val id: Int, val name: String) : Parcelable

            // Naviguer avec un minimum de données
navController.navigate("profile/42")

            // Récupérer les arguments sur l'écran
composable(
    route = "profile/{userId}",
    arguments = listOf(
        NavArgument("userId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val userId = backStackEntry.arguments?.getInt("userId") ?: 0
    ProfileDetailScreen(userId = userId)
}

Navigation imbriquée avec composable()

Dans les applications réelles, il est souvent nécessaire d'organiser des graphes de navigation imbriqués — par exemple, une pile d'écrans séparée à l'intérieur d'un onglet BottomNavigation. composable() prend en charge l'imbrication via des NavHost imbriqués : à l'intérieur d'un écran composable, vous pouvez déclarer votre propre NavHost avec une pile de routes indépendante.

Chaque NavHost imbriqué a son propre NavController et sa propre pile arrière. Cela signifie que la navigation à l'intérieur d'un onglet n'affecte pas la navigation dans les autres onglets — l'utilisateur peut basculer librement entre les onglets sans perdre l'historique de navigation dans chacun d'eux. Cette architecture est appelée Scoped Navigation et est recommandée par Google pour les applications avec une navigation complexe à plusieurs niveaux.

Lors de l'implémentation de la navigation imbriquée, il est important de gérer correctement l'état du NavController : chaque NavHost imbriqué doit stocker son propre rememberNavController dans la portée de la fonction composable. Selon l'Android Developer Summit 2024, plus de 40% des applications Jetpack Compose avec trois onglets ou plus utilisent l'architecture NavHost imbriquée pour isoler la navigation entre les modules.

kotlin
// NavHost principal avec onglets
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// Graphe imbriqué dans l'onglet Accueil
@Composable
fun HomeNavGraph() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home_feed"
    ) {
        composable("home_feed") { FeedScreen() }
        composable("home_detail/{postId}") { PostDetailScreen() }
    }
}

composable() vs navigation par Intent

Avant Jetpack Compose, la méthode standard de navigation dans Android utilisait Intent et FragmentManager. Intent est un message système qui lance une nouvelle Activity, ce qui implique la recréation de tout l'arbre View. En revanche, composable() fonctionne à l'intérieur d'une seule Activity et remplace simplement une partie de l'arbre Compose, ce qui est significativement plus rapide et plus efficace en mémoire.

Principales différences entre composable() et la navigation basée sur Intent :

  • Vitesse — composable() change d'écran en millisecondes sans recréer l'Activity ; Intent nécessite un redémarrage de l'Activity.
  • Animations — dans Navigation Compose, les animations de transition sont définies déclarativement via AnimatedNavHost, sans besoin d'overridePendingTransition.
  • État partagé — composable() fonctionne dans une portée ViewModel partagée, simplifiant le transfert de données entre écrans sans Intent extras.
Caractéristiquecomposable()Intent / Fragment
ArchitectureSingle Activity, arbre ComposeMulti Activity, piles Fragment
Transfert de donnéesparamètres path/query, ViewModel partagéIntent extras, Bundle, SharedPreferences
Liens profondsSupport intégré navDeepLinkintent-filter dans le manifeste
Pile arrièreGestion automatique popBackStackFragmentManager.popBackStack()
Temps de changement5–15 ms (intra-processus)50–200 ms (avec recréation)

Passer d'Intent à composable() n'est pas seulement un remplacement d'API, mais un changement de paradigme architectural. Au lieu de spécifier explicitement quelle Activity doit s'ouvrir, le développeur décrit déclarativement toutes les routes possibles en un seul endroit, ce qui améliore la lisibilité du code et simplifie les tests de navigation. Selon Google I/O 2024, Jetpack Compose avec Navigation Compose réduit le code de navigation de 40 à 60% par rapport à FragmentManager.

Erreurs courantes avec composable()

L'une des erreurs les plus courantes est la recréation du NavController pendant la recomposition. Si NavController est créé via rememberNavController() au niveau du composable parent, qui peut être recréé lors d'un changement d'état, la navigation se brise — l'historique est perdu. La solution correcte est d'élever NavController à un niveau de composable stable, comme le niveau de l'Activity ou le composable racine de l'application.

Le deuxième problème courant est la recomposition infinie pendant la navigation. Cela se produit lorsque navController.navigate() est placé directement dans le corps d'une fonction composable. Comme la navigation modifie l'état de NavHost, cela déclenche une recomposition, qui appelle à nouveau navigate(), créant une boucle. Tous les appels de navigation doivent être encapsulés dans des lambdas de gestionnaires (onClick, onButtonPressed), et non exécutés dans la composition.

La troisième erreur est la gestion incorrecte de la pile arrière lors de l'utilisation de BottomNavigation. La navigation simple via navigate() à chaque changement d'onglet ajoute une nouvelle entrée à la pile au lieu de revenir à l'existante. Pour BottomNavigation, vous devez utiliser navController.navigate() avec restoreState = true et launchSingleTop = true, ce qui garantit une restauration correcte de l'état lors du changement d'onglet.

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

Questions fréquentes

Quelle est la différence entre composable() et une fonction @Composable normale ?

composable() n'est pas une annotation, mais une fonction d'extension de NavHost qui lie une route à l'UI. Une fonction @Composable normale décrit simplement la mise en page, tandis que composable() enregistre cette mise en page dans le graphe de navigation avec une route spécifiée, la rendant accessible pour la navigation via NavController.

Comment passer un objet complexe entre les écrans composable() ?

Il est recommandé de passer seulement un identifiant (ID) via le paramètre de chemin, et de charger l'objet sur l'écran par ID via un dépôt ou ViewModel. Si vous devez quand même passer l'objet, utilisez NavType.ParcelableType, mais évitez de passer des objets de plus de 1 Ko — cela peut entraîner une TransactionTooLargeException.

Pourquoi l'écran composable() est-il recréé lors de la rotation de l'écran ?

La rotation de l'écran provoque un changement de configuration, qui recrée l'Activity par défaut. Pour préserver l'état des écrans composable, utilisez rememberSaveable pour les données simples ou ViewModel avec la portée de cet écran. Navigation Compose restaure la pile arrière après la recréation, mais l'état à l'intérieur des fonctions composable() est réinitialisé sans rememberSaveable.

Peut-on utiliser composable() sans NavHost ?

Non, composable() est une fonction d'extension de NavGraphBuilder, qui n'est disponible qu'à l'intérieur du bloc NavHost. Pour un simple remplacement d'UI sans navigation, utilisez le rendu conditionnel (when, if) ou AnimatedContent. composable() est conçu spécifiquement pour le routage avec prise en charge de la pile arrière et des liens profonds.

Comment distinguer une première navigation d'une navigation arrière dans composable() ?

Utilisez SavedStateHandle dans ViewModel : lors de la première navigation, handle.get("initialized") retourne null ; lors de la navigation arrière, il retourne la valeur sauvegardée. Alternativement, analysez la position actuelle dans la pile arrière via navController.previousBackStackEntry — si elle est null, c'est le premier écran dans la pile de navigation.

Résumé

  • composable() est une fonction d'enregistrement d'écran dans NavHost, la principale façon d'organiser la navigation dans Jetpack Compose.
  • Routes — chaque écran est identifié par une chaîne de route avec des paramètres de chemin et de requête optionnels.
  • Arguments — sont passés via NavArgument avec prise en charge des types primitifs, Parcelable et Serializable.
  • Imbrication — composable() prend en charge les NavHost imbriqués pour organiser une navigation modulaire avec des piles indépendantes.
  • Performance — l'initialisation paresseuse des écrans économise la mémoire, la vitesse de changement d'écran est de 5 à 15 ms.
  • Erreurs — principaux problèmes : recréation du NavController, recomposition infinie avec navigate() dans le corps du composable, mauvaise gestion de BottomNavigation.
  • Migration — passer de FragmentManager à composable() réduit le volume de code de navigation de 40 à 60% et élimine une classe de bugs liés au cycle de vie de Fragment.

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.

Discuter du projet

Lisez aussi