Jetpack Navigation Compose en 2026 : Navigation Type-Safe et Questions d'Entretien

Maîtrisez Jetpack Navigation Compose avec la navigation type-safe en Kotlin. Guide complet avec exemples de code, gestion des arguments et questions d'entretien Android fréquentes en 2026.

Jetpack Navigation Compose en 2026 : Navigation Type-Safe et Questions d'Entretien

Jetpack Navigation Compose représente la solution moderne de navigation pour les applications Android développées avec Jetpack Compose. En 2026, cette bibliothèque a considérablement évolué pour offrir une navigation entièrement type-safe grâce aux améliorations de Kotlin et aux nouvelles API introduites par Google. La maîtrise de ces concepts constitue un atout majeur lors des entretiens techniques Android.

Depuis Navigation Compose 2.8.0, la navigation type-safe utilise les classes sérialisables Kotlin au lieu des routes sous forme de chaînes de caractères. Cette approche élimine les erreurs de typage à l'exécution et améliore considérablement la maintenabilité du code.

Configuration de Navigation Compose Type-Safe

La mise en place d'une navigation type-safe nécessite l'utilisation du plugin de sérialisation Kotlin et la définition de destinations sous forme d'objets ou de classes de données. Cette approche remplace les anciennes routes basées sur des chaînes de caractères.

build.gradle.ktskotlin
plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
}

dependencies {
    implementation("androidx.navigation:navigation-compose:2.9.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
}

La configuration du projet requiert le plugin de sérialisation Kotlin ainsi que la dernière version de Navigation Compose. Le plugin de sérialisation permet de convertir automatiquement les objets de destination en routes navigables.

Navigation.ktkotlin
import kotlinx.serialization.Serializable

@Serializable
object Home

@Serializable
object Profile

@Serializable
data class ProductDetail(val productId: String)

@Serializable
data class Search(val query: String = "", val category: String? = null)

Les destinations sont définies comme des objets sérialisables pour les écrans sans paramètres et comme des classes de données pour les écrans nécessitant des arguments. Les valeurs par défaut et les types nullables permettent de gérer les paramètres optionnels.

Construction du NavHost avec des Routes Type-Safe

Le NavHost utilise désormais les types Kotlin directement au lieu des chaînes de caractères pour définir les destinations et les arguments de navigation.

AppNavigation.ktkotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = Home
    ) {
        composable<Home> {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate(Profile)
                },
                onNavigateToProduct = { productId ->
                    navController.navigate(ProductDetail(productId))
                }
            )
        }
        
        composable<Profile> {
            ProfileScreen(
                onNavigateBack = { navController.popBackStack() }
            )
        }
        
        composable<ProductDetail> { backStackEntry ->
            val product: ProductDetail = backStackEntry.toRoute()
            ProductDetailScreen(
                productId = product.productId,
                onNavigateBack = { navController.popBackStack() }
            )
        }
        
        composable<Search> { backStackEntry ->
            val search: Search = backStackEntry.toRoute()
            SearchScreen(
                initialQuery = search.query,
                category = search.category
            )
        }
    }
}

La fonction toRoute() extrait automatiquement les paramètres typés depuis l'entrée de la pile de navigation. Cette méthode garantit la sécurité du typage à la compilation et élimine les erreurs de parsing des arguments.

La navigation type-safe permet de passer des objets complexes entre les écrans en utilisant la sérialisation Kotlin. Les types personnalisés nécessitent un NavType adapté.

CustomNavTypes.ktkotlin
import android.os.Bundle
import androidx.navigation.NavType
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class User(
    val id: String,
    val name: String,
    val email: String
)

@Serializable
data class UserProfile(val user: User)

val UserNavType = object : NavType<User>(isNullableAllowed = false) {
    override fun get(bundle: Bundle, key: String): User? {
        return bundle.getString(key)?.let { Json.decodeFromString(it) }
    }
    
    override fun parseValue(value: String): User {
        return Json.decodeFromString(value)
    }
    
    override fun put(bundle: Bundle, key: String, value: User) {
        bundle.putString(key, Json.encodeToString(value))
    }
    
    override fun serializeAsValue(value: User): String {
        return Json.encodeToString(value)
    }
}

Les types personnalisés sont sérialisés en JSON pour le passage entre les destinations. Cette approche permet de transmettre des données structurées tout en maintenant la sécurité du typage.

NavHostWithCustomType.ktkotlin
@Composable
fun AppNavigationWithCustomTypes() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = Home
    ) {
        composable<Home> {
            HomeScreen(
                onUserSelected = { user ->
                    navController.navigate(UserProfile(user))
                }
            )
        }
        
        composable<UserProfile>(
            typeMap = mapOf(typeOf<User>() to UserNavType)
        ) { backStackEntry ->
            val userProfile: UserProfile = backStackEntry.toRoute()
            UserProfileScreen(user = userProfile.user)
        }
    }
}

Le paramètre typeMap associe les types personnalisés à leurs NavType correspondants, permettant la sérialisation et la désérialisation automatiques lors de la navigation.

Gestion de la Pile de Navigation

La manipulation de la pile de navigation constitue un aspect essentiel pour créer des flux utilisateur fluides. Les options de navigation permettent de contrôler précisément le comportement de la pile.

NavigationHelpers.ktkotlin
fun NavController.navigateWithClearBackStack(destination: Any) {
    navigate(destination) {
        popUpTo(graph.startDestinationId) {
            inclusive = true
        }
        launchSingleTop = true
    }
}

fun NavController.navigateSingleTop(destination: Any) {
    navigate(destination) {
        launchSingleTop = true
        restoreState = true
    }
}

fun NavController.navigateAndPopUpTo(
    destination: Any,
    popUpToDestination: Any,
    inclusive: Boolean = false
) {
    navigate(destination) {
        popUpTo(popUpToDestination) {
            this.inclusive = inclusive
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

Ces fonctions d'extension simplifient les patterns de navigation courants. L'option launchSingleTop évite les doublons dans la pile, tandis que saveState et restoreState préservent l'état des écrans.

L'intégration de Navigation Compose avec une barre de navigation inférieure nécessite une synchronisation entre les onglets et les destinations.

BottomNavigation.ktkotlin
@Serializable
sealed class TopLevelDestination {
    @Serializable
    data object Home : TopLevelDestination()
    
    @Serializable
    data object Search : TopLevelDestination()
    
    @Serializable
    data object Favorites : TopLevelDestination()
    
    @Serializable
    data object Profile : TopLevelDestination()
}

@Composable
fun MainScreenWithBottomNav() {
    val navController = rememberNavController()
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination
    
    Scaffold(
        bottomBar = {
            NavigationBar {
                listOf(
                    TopLevelDestination.Home to Icons.Default.Home,
                    TopLevelDestination.Search to Icons.Default.Search,
                    TopLevelDestination.Favorites to Icons.Default.Favorite,
                    TopLevelDestination.Profile to Icons.Default.Person
                ).forEach { (destination, icon) ->
                    NavigationBarItem(
                        icon = { Icon(icon, contentDescription = null) },
                        selected = currentDestination?.hasRoute(
                            destination::class
                        ) == true,
                        onClick = {
                            navController.navigate(destination) {
                                popUpTo(navController.graph.startDestinationId) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )
                }
            }
        }
    ) { padding ->
        NavHost(
            navController = navController,
            startDestination = TopLevelDestination.Home,
            modifier = Modifier.padding(padding)
        ) {
            composable<TopLevelDestination.Home> { HomeScreen() }
            composable<TopLevelDestination.Search> { SearchScreen() }
            composable<TopLevelDestination.Favorites> { FavoritesScreen() }
            composable<TopLevelDestination.Profile> { ProfileScreen() }
        }
    }
}

La fonction hasRoute() vérifie si la destination actuelle correspond à une classe de route spécifique. Les options de navigation assurent la préservation de l'état entre les onglets.

La configuration des deep links avec la navigation type-safe permet de gérer les liens entrants de manière sécurisée.

DeepLinkNavigation.ktkotlin
@Serializable
data class Article(val articleId: String)

@Composable
fun DeepLinkNavHost() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = Home
    ) {
        composable<Home> { HomeScreen() }
        
        composable<Article>(
            deepLinks = listOf(
                navDeepLink<Article>(
                    basePath = "https://example.com/article"
                )
            )
        ) { backStackEntry ->
            val article: Article = backStackEntry.toRoute()
            ArticleScreen(articleId = article.articleId)
        }
    }
}

La fonction navDeepLink génère automatiquement le pattern de deep link basé sur la structure de la classe de destination. Les paramètres sont extraits du chemin URL et mappés aux propriétés correspondantes.

Nested Navigation Graphs

L'organisation du code de navigation en graphes imbriqués améliore la structure et la maintenabilité des applications complexes.

NestedNavigation.ktkotlin
@Serializable
object AuthGraph

@Serializable
object Login

@Serializable
object Register

@Serializable
object ForgotPassword

@Composable
fun AppWithNestedNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = AuthGraph
    ) {
        navigation<AuthGraph>(startDestination = Login) {
            composable<Login> {
                LoginScreen(
                    onNavigateToRegister = {
                        navController.navigate(Register)
                    },
                    onNavigateToForgotPassword = {
                        navController.navigate(ForgotPassword)
                    },
                    onLoginSuccess = {
                        navController.navigateWithClearBackStack(Home)
                    }
                )
            }
            
            composable<Register> {
                RegisterScreen(
                    onNavigateBack = { navController.popBackStack() }
                )
            }
            
            composable<ForgotPassword> {
                ForgotPasswordScreen(
                    onNavigateBack = { navController.popBackStack() }
                )
            }
        }
        
        composable<Home> {
            HomeScreen()
        }
    }
}

Les graphes imbriqués permettent de regrouper les destinations liées et de définir des points d'entrée distincts pour chaque flux utilisateur.

Prêt à réussir tes entretiens Android ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Questions d'Entretien sur Navigation Compose

Les entretiens techniques Android en 2026 incluent fréquemment des questions sur la navigation type-safe. La compréhension approfondie de ces concepts démontre une maîtrise des pratiques modernes de développement.

Question : Quels sont les avantages de la navigation type-safe par rapport aux routes basées sur des chaînes de caractères ?

La navigation type-safe offre plusieurs avantages majeurs. Les erreurs de typage sont détectées à la compilation plutôt qu'à l'exécution, éliminant les crashes liés aux arguments mal typés. La refactorisation devient sûre car le compilateur identifie tous les usages lors du renommage des destinations. L'auto-complétion de l'IDE fonctionne correctement, améliorant la productivité. La documentation du code est implicite grâce aux types explicites des paramètres.

Question : Comment passer des objets complexes entre les destinations avec Navigation Compose ?

Le passage d'objets complexes nécessite la création d'un NavType personnalisé qui gère la sérialisation et la désérialisation. L'objet est converti en chaîne JSON pour le transport et reconverti en objet typé à la destination. Le typeMap dans la définition de la composable associe le type Kotlin au NavType correspondant. Cette approche maintient la sécurité du typage tout en permettant le passage de données structurées.

Question : Quelle est la différence entre popBackStack() et navigateUp() ?

La méthode popBackStack() retire l'écran actuel de la pile de navigation et retourne à l'écran précédent dans la pile locale de l'application. La méthode navigateUp() tente de naviguer vers le parent hiérarchique défini dans le graphe de navigation, ce qui peut différer de la pile réelle. Pour les deep links, navigateUp() peut créer une pile synthétique vers le parent, tandis que popBackStack() peut fermer l'application si la pile est vide.

Question : Comment préserver l'état des onglets dans une Bottom Navigation ?

La préservation de l'état nécessite l'utilisation des options saveState et restoreState lors de la navigation. L'option popUpTo avec saveState = true sauvegarde l'état de la destination courante avant de naviguer. L'option restoreState = true restaure l'état précédemment sauvegardé lors du retour à un onglet. Cette combinaison permet de maintenir la position de défilement et les données des formulaires.

Question : Comment gérer les deep links avec des paramètres dans Navigation Compose type-safe ?

Les deep links type-safe utilisent la fonction navDeepLink avec un basePath qui correspond à l'URL de base. Les paramètres de la classe de destination sont automatiquement extraits du chemin URL ou des query parameters. La validation des paramètres est effectuée par le système de sérialisation, garantissant que les valeurs reçues correspondent aux types attendus.

Question : Quand utiliser des graphes de navigation imbriqués ?

Les graphes imbriqués sont appropriés pour regrouper des écrans liés fonctionnellement, comme le flux d'authentification ou le processus de paiement. Cette organisation permet de naviguer vers un graphe entier comme une unité et de définir des points d'entrée distincts. Les graphes imbriqués facilitent également la réutilisation de flux de navigation entre différentes parties de l'application.

Question : Comment tester la navigation dans une application Compose ?

Les tests de navigation utilisent TestNavHostController avec les règles de test Compose. L'assertion navController.currentBackStackEntry?.toRoute<T>() vérifie la destination actuelle. Les tests peuvent simuler des navigations et vérifier l'état de la pile. L'injection du NavController via les paramètres des composables facilite le remplacement par un mock lors des tests.

Conclusion

Jetpack Navigation Compose avec la navigation type-safe représente une évolution majeure pour le développement Android. L'utilisation des classes sérialisables Kotlin élimine les erreurs de typage et améliore significativement la maintenabilité du code. La maîtrise des concepts présentés dans cet article, incluant la configuration initiale, la gestion des arguments complexes, les deep links et les graphes imbriqués, constitue une base solide pour développer des applications Android modernes.

La préparation aux entretiens techniques nécessite une compréhension approfondie de ces mécanismes. Les recruteurs évaluent la capacité à expliquer les choix architecturaux et à résoudre les problèmes courants de navigation. La pratique régulière avec des projets réels complète efficacement l'apprentissage théorique de Navigation Compose.

Défi du jour

Tu saurais repérer le bug en Android ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 1 septembre 2026

Partager

Articles similaires