Esegui la migrazione di Jetpack Navigation a Navigation Compose

L'API Navigation Compose ti consente di navigare tra i composable in un'app Compose, sfruttando al contempo il componente, l'infrastruttura e le funzionalità di Jetpack Navigation.

Questa pagina descrive come eseguire la migrazione da una navigazione Jetpack basata su Fragment a Navigation Compose, nell'ambito della migrazione più ampia dell'interfaccia utente basata su View a Jetpack Compose.

Prerequisiti per la migrazione

Puoi eseguire la migrazione a Navigation Compose una volta che sei in grado di sostituire tutti i tuoi fragment con i composable dello schermo corrispondenti. I composable schermata possono contenere un mix di contenuti Compose e View, ma tutte le destinazioni di navigazione devono essere composable per consentire la migrazione di Navigation Compose. Fino ad allora, devi continuare a utilizzare il componente di navigazione basato su frammenti nel codice base di View e Compose per l'interoperabilità. Per ulteriori informazioni, consulta la documentazione sull'interoperabilità della navigazione.

L'utilizzo di Navigation Compose in un'app solo Compose non è un prerequisito. Puoi continuare a utilizzare il componente Navigazione basata su frammenti, a condizione che tu mantenga i frammenti per ospitare i tuoi contenuti componibili.

Passaggi per la migrazione

Che tu stia seguendo la nostra strategia di migrazione consigliata o adottando un altro approccio, arriverai a un punto in cui tutte le destinazioni di navigazione sono componenti componibili dello schermo, con i frammenti che fungono solo da contenitori componibili. In questa fase, puoi eseguire la migrazione a Navigation Compose.

Se la tua app segue già un pattern di progettazione UDF e la nostra guida all'architettura, la migrazione a Jetpack Compose e Navigation Compose non dovrebbe richiedere refactoring importanti di altri livelli dell'app, a parte il livello UI.

Per eseguire la migrazione a Navigation Compose:

  1. Aggiungi la dipendenza Navigation Compose alla tua app.
  2. Crea un composable App-level e aggiungilo a Activity come punto di ingresso di Compose, sostituendo la configurazione del layout View:

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

  3. Crea tipi per ogni destinazione di navigazione. Utilizza un data object per le destinazioni che non richiedono dati e data class o class per le destinazioni che richiedono dati.

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

  4. Configura NavController in un punto in cui tutti i composable che devono farvi riferimento possano accedervi (di solito all'interno del composable App). Questo approccio segue i principi di sollevamento dello stato e ti consente di utilizzare NavController come fonte di verità per navigare tra le schermate componibili e mantenere lo stack precedente:

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

  5. Crea l'NavHost della tua app all'interno del composable App e passa navController:

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

  6. Aggiungi le destinazioni composable per creare il grafico di navigazione. Se ogni schermata è stata migrata in precedenza a Compose, questo passaggio consiste solo nell'estrazione di questi composable della schermata dai frammenti nelle destinazioni 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. Se hai seguito le indicazioni per progettare l'interfaccia utente di Compose, in particolare come passare gli ViewModel e gli eventi di navigazione ai composable, il passaggio successivo consiste nel modificare il modo in cui fornisci ViewModel a ogni composable dello schermo. Spesso puoi utilizzare l'inserimento di Hilt e il relativo punto di integrazione con Compose e Navigation tramite hiltViewModel:

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

  8. Sostituisci tutte le chiamate di navigazione findNavController() con quelle navController e passale come eventi di navigazione a ogni schermata componibile, anziché passare l'intero navController. Questo approccio segue le best practices di esposizione degli eventi dalle funzioni componibili ai chiamanti e mantiene navController come unica fonte attendibile.

    I dati possono essere trasmessi a una destinazione creando un'istanza della classe di route definita per quella destinazione. Può essere ottenuto direttamente dall'entry del back stack nella destinazione o da un ViewModel utilizzando 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. Rimuovi tutti i fragment, i layout XML pertinenti, la navigazione non necessaria e altre risorse, nonché le dipendenze obsolete di Fragment e Jetpack Navigation.

Puoi trovare gli stessi passaggi con maggiori dettagli relativi a Navigation Compose nella documentazione di configurazione.

Casi d'uso comuni

Indipendentemente dal componente di navigazione che utilizzi, si applicano gli stessi principi di navigazione.

I casi d'uso comuni durante la migrazione includono:

Per informazioni più dettagliate su questi casi d'uso, consulta Navigare con Scrivi.

Recuperare dati complessi durante la navigazione

Ti consigliamo vivamente di non passare oggetti dati complessi durante la navigazione. Invece, passa le informazioni minime necessarie, come un identificatore univoco o un'altra forma di ID, come argomenti durante l'esecuzione delle azioni di navigazione. Devi archiviare gli oggetti complessi come dati in un'unica fonte attendibile, ad esempio il livello dati. Per ulteriori informazioni, vedi Recuperare dati complessi durante la navigazione.

Se i tuoi fragment passano oggetti complessi come argomenti, valuta la possibilità di eseguire il refactoring del codice in modo da consentire l'archiviazione e il recupero di questi oggetti dal livello dati. Consulta il repository Now in Android per esempi.

Limitazioni

Questa sezione descrive le limitazioni attuali di Navigation Compose.

Migrazione incrementale a Navigation Compose

Al momento non puoi utilizzare Navigation Compose mentre utilizzi ancora i fragment come destinazioni nel codice. Per iniziare a utilizzare Navigation Compose, tutte le tue destinazioni devono essere composable. Puoi monitorare questa richiesta di funzionalità su Issue Tracker.

Animazioni di transizione

A partire da Navigation 2.7.0-alpha01, il supporto per l'impostazione di transizioni personalizzate, in precedenza da AnimatedNavHost, è ora supportato direttamente in NavHost. Per ulteriori informazioni, leggi le note di rilascio.

Scopri di più

Per saperne di più sulla migrazione a Navigation Compose, consulta le seguenti risorse:

  • Codelab Navigation Compose: scopri le nozioni di base di Navigation Compose con un codelab pratico.
  • Ora nel repository Now in Android: un'app per Android completamente funzionante creata interamente con Kotlin e Jetpack Compose, che segue le best practice di progettazione e sviluppo di Android e include Navigation Compose.
  • Migrazione di Sunflower a Jetpack Compose: un post del blog che documenta il percorso di migrazione dell'app di esempio Sunflower da Views a Compose, che include anche la migrazione a Navigation Compose.
  • Jetnews per ogni schermo: un post del blog che documenta il refactoring e la migrazione dell'esempio Jetnews per supportare tutti gli schermi con Jetpack Compose e Navigation Compose.