Migrer la navigation Jetpack vers Navigation Compose

L'API Navigation Compose vous permet de naviguer entre les composables d'une application Compose, tout en tirant parti du composant, de l'infrastructure et des fonctionnalités de Jetpack Navigation.

Cette page décrit comment migrer d'une navigation Jetpack basée sur les fragments vers Navigation Compose, dans le cadre de la migration plus large de l'UI basée sur les vues vers Jetpack Compose.

Conditions préalables à la migration

Vous pouvez migrer vers Navigation Compose une fois que vous êtes en mesure de remplacer tous vos fragments par les composables d'écran correspondants. Les composables d'écran peuvent contenir un mélange de contenu Compose et View, mais toutes les destinations de navigation doivent être des composables pour permettre la migration de Navigation Compose. D'ici là, vous devez continuer à utiliser le composant Navigation basé sur des fragments dans votre codebase d'interopérabilité View et Compose. Pour en savoir plus, consultez la documentation sur l'interopérabilité de la navigation.

Il n'est pas nécessaire d'utiliser Navigation Compose dans une application Compose uniquement. Vous pouvez continuer à utiliser le composant Navigation basé sur les fragments, à condition de conserver les fragments pour héberger votre contenu composable.

Procédure de migration

Que vous suiviez notre stratégie de migration recommandée ou que vous adoptiez une autre approche, vous arriverez à un point où toutes les destinations de navigation seront des composables d'écran, les fragments agissant uniquement en tant que conteneurs composables. À ce stade, vous pouvez migrer vers Navigation Compose.

Si votre application suit déjà un schéma de conception UDF et notre guide d'architecture, la migration vers Jetpack Compose et Navigation Compose ne devrait pas nécessiter de refactorisation majeure des autres couches de votre application, à l'exception de la couche d'interface utilisateur.

Pour migrer vers Navigation Compose, procédez comme suit :

  1. Ajoutez la dépendance Navigation Compose à votre application.
  2. Créez un composable App-level et ajoutez-le à votre Activity en tant que point d'entrée Compose, en remplaçant la configuration de la mise en page View :

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. Créez des types pour chaque destination de navigation. Utilisez data object pour les destinations qui ne nécessitent aucune donnée, et data class ou class pour celles qui en nécessitent.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. Configurez NavController à un endroit où tous les composables qui doivent le référencer peuvent y avoir accès (généralement à l'intérieur de votre composable App). Cette approche suit les principes du hissage d'état et vous permet d'utiliser NavController comme source d'informations pour naviguer entre les écrans composables et gérer la pile "Retour" :

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Créez le NavHost de votre application dans le composable App et transmettez le navController :

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. Ajoutez les destinations composable pour créer votre graphique de navigation. Si chaque écran a déjà été migré vers Compose, cette étape consiste uniquement à extraire ces composables d'écran de vos Fragments vers les destinations composable :

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. Si vous avez suivi les conseils sur l'architecture de votre UI Compose, en particulier sur la façon dont les ViewModel et les événements de navigation doivent être transmis aux composables, l'étape suivante consiste à modifier la façon dont vous fournissez le ViewModel à chaque composable d'écran. Vous pouvez souvent utiliser l'injection Hilt et son point d'intégration avec Compose et Navigation via hiltViewModel :

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. Remplacez tous les appels de navigation findNavController() par ceux de navController et transmettez-les en tant qu'événements de navigation à chaque écran composable, plutôt que de transmettre l'ensemble de navController. Cette approche suit les bonnes pratiques d'exposition des événements des fonctions composables aux appelants et maintient navController comme référence unique.

    Les données peuvent être transmises à une destination en créant une instance de la classe de route définie pour cette destination. Il peut ensuite être obtenu directement à partir de l'entrée de la pile "Retour" à la destination ou à partir d'un ViewModel à l'aide de SavedStateHandle.toRoute().

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. Supprimez tous les fragments, les mises en page XML pertinentes, la navigation inutile et les autres ressources, ainsi que les dépendances obsolètes de Fragment et de Jetpack Navigation.

Vous trouverez les mêmes étapes avec plus de détails sur Navigation Compose dans la documentation de configuration.

Cas d'utilisation courants

Quel que soit le composant Navigation que vous utilisez, les mêmes principes de navigation s'appliquent.

Voici quelques cas d'utilisation courants lors de la migration :

Pour en savoir plus sur ces cas d'utilisation, consultez Naviguer avec Compose.

Récupérer des données complexes lors de la navigation

Nous vous recommandons vivement de ne pas transmettre d'objets de données complexes lors de la navigation. Au lieu de cela, transmettez le strict minimum (comme un identifiant unique ou une autre forme d'ID) sous la forme d'arguments lorsque vous effectuez des actions de navigation. Vous devez stocker les objets complexes sous forme de données dans une source unique de référence, telle que la couche de données. Pour en savoir plus, consultez Récupérer des données complexes lors de la navigation.

Si vos Fragments transmettent des objets complexes en tant qu'arguments, envisagez de refactoriser votre code en premier lieu, de manière à pouvoir stocker et récupérer ces objets à partir de la couche de données. Consultez le dépôt "En ce moment sur Android" pour obtenir des exemples.

Limites

Cette section décrit les limites actuelles de Navigation Compose.

Migration incrémentielle vers Navigation Compose

Actuellement, vous ne pouvez pas utiliser Navigation Compose tout en utilisant des fragments comme destinations dans votre code. Pour commencer à utiliser Navigation Compose, toutes vos destinations doivent être des composables. Vous pouvez suivre cette demande de fonctionnalité dans l'outil de suivi des problèmes.

Animations de transition

À partir de Navigation 2.7.0-alpha01, la définition de transitions personnalisées, auparavant à partir de AnimatedNavHost, est désormais directement prise en charge dans NavHost. Pour en savoir plus, consultez les notes de version.

En savoir plus

Pour en savoir plus sur la migration vers Navigation Compose, consultez les ressources suivantes :

  • Atelier de programmation Navigation Compose : découvrez les bases de Navigation Compose grâce à un atelier de programmation pratique.
  • Dépôt Now in Android : application Android entièrement fonctionnelle, développée entièrement avec Kotlin et Jetpack Compose, qui suit les bonnes pratiques de conception et de développement Android, et inclut Navigation Compose.
  • Migrer Sunflower vers Jetpack Compose : article de blog qui documente le parcours de migration de l'application exemple Sunflower des vues vers Compose, qui inclut également la migration vers Navigation Compose.
  • Jetnews pour tous les écrans : article de blog qui documente la refactorisation et la migration de l'exemple Jetnews pour prendre en charge tous les écrans avec Jetpack Compose et Navigation Compose.