Interagire in modo programmatico con il componente Navigazione

Il componente Navigazione fornisce metodi per creare e interagire in modo programmatico con determinati elementi di navigazione.

Crea un NavHostFragment

Puoi utilizzare NavHostFragment.create() per creare in modo programmatico un NavHostFragment con una specifica risorsa grafico, come illustrato nell'esempio seguente:

Kotlin

val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit()

Java

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit();

Tieni presente che setPrimaryNavigationFragment(finalHost) consente al tuo NavHost di intercettare le pressioni del pulsante Indietro del sistema. Puoi anche implementare questo comportamento nel tuo XML NavHost aggiungendo app:defaultNavHost="true". Se stai implementando un comportamento del pulsante Indietro personalizzato e non vuoi che NavHost intercetta le pressioni del pulsante Indietro, puoi passare null a setPrimaryNavigationFragment().

A partire da Navigation 2.2.0, puoi ottenere un riferimento a NavBackStackEntry per qualsiasi destinazione nello stack di navigazione chiamando NavController.getBackStackEntry(), passandogli un ID destinazione. Se lo stack posteriore contiene più di un'istanza della destinazione specificata, getBackStackEntry() restituisce l'istanza più in alto dallo stack.

Il valore NavBackStackEntry restituito fornisce Lifecycle, ViewModelStore e SavedStateRegistry a livello di destinazione. Questi oggetti sono validi per la durata della destinazione sullo stack posteriore. Quando la destinazione associata esce dallo stack posteriore, l'oggetto Lifecycle viene eliminato, lo stato non viene più salvato e gli oggetti ViewModel vengono cancellati.

Queste proprietà offrono un Lifecycle e un archivio per ViewModel oggetti e classi che funzionano con lo stato salvato, indipendentemente dal tipo di destinazione che utilizzi. Questo è particolarmente utile quando lavori con tipi di destinazione a cui non è associato automaticamente un Lifecycle, come le destinazioni personalizzate.

Ad esempio, puoi osservare il Lifecycle di un NavBackStackEntry proprio come osserveresti il Lifecycle di un frammento o di un'attività. Inoltre, NavBackStackEntry è un LifecycleOwner, il che significa che puoi utilizzarlo quando osservi LiveData o con altri componenti sensibili al ciclo di vita, come mostrato nell'esempio seguente:

Kotlin

myViewModel.liveData.observe(backStackEntry, Observer { myData ->
    // react to live data update
})

Java

myViewModel.getLiveData().observe(backStackEntry, myData -> {
    // react to live data update
});

Lo stato del ciclo di vita si aggiorna automaticamente ogni volta che chiami navigate(). Gli stati del ciclo di vita per le destinazioni che non si trovano nella parte superiore dello stack arretrato vengono spostati da RESUMED a STARTED se le destinazioni sono ancora visibili in una destinazione FloatingWindow, ad esempio la destinazione di una finestra di dialogo, oppure in STOPPED.

Ritorno di un risultato alla destinazione precedente

In Navigazione 2.3 e versioni successive, NavBackStackEntry concede l'accesso a un SavedStateHandle. Una SavedStateHandle è una mappa chiave-valore che può essere utilizzata per archiviare e recuperare i dati. Questi valori rimangono disponibili fino all'interruzione del processo, comprese le modifiche alla configurazione, e rimangono disponibili attraverso lo stesso oggetto. Utilizzando l'SavedStateHandle specificato, puoi accedere ai dati e passare da una destinazione all'altra. Questo è particolarmente utile come meccanismo per recuperare i dati da una destinazione dopo che sono stati estratti dallo stack.

Per ritrasmettere i dati alla destinazione A dalla destinazione B, configura prima la destinazione A per ascoltare un risultato sul relativo SavedStateHandle. Per farlo, recupera NavBackStackEntry utilizzando l'API getCurrentBackStackEntry(), quindi observe il LiveData fornito da SavedStateHandle.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
            .getSavedStateHandle()
            .getLiveData("key");
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            // Do something with the result.
        }
    });
}

Nella destinazione B, devi set il risultato in SavedStateHandle della destinazione A utilizzando l'API getPreviousBackStackEntry().

Kotlin

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

Java

navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);

Se vuoi gestire un risultato solo una volta, devi chiamare remove() su SavedStateHandle per cancellare il risultato. Se non rimuovi il risultato, LiveData continuerà a restituire l'ultimo risultato a tutte le nuove istanze Observer.

Considerazioni sull'utilizzo delle destinazioni delle finestre di dialogo

Quando esegui il navigate su una destinazione che ha la visione completa di NavHost (ad esempio una destinazione <fragment>), il ciclo di vita della destinazione precedente è stato interrotto, impedendo eventuali callback alla destinazione LiveData fornita da SavedStateHandle.

Tuttavia, quando si accede a una destinazione finestra di dialogo, sullo schermo è visibile anche la destinazione precedente, che corrisponde pertanto a STARTED nonostante non sia la destinazione corrente. Ciò significa che le chiamate a getCurrentBackStackEntry() da metodi del ciclo di vita come onViewCreated() restituiranno il valore NavBackStackEntry della destinazione della finestra di dialogo dopo una modifica della configurazione o un processo di interruzione e ricreazione (poiché la finestra di dialogo viene ripristinata sopra l'altra destinazione). Pertanto, devi utilizzare getBackStackEntry() con l'ID della destinazione per assicurarti di usare sempre la NavBackStackEntry corretta.

Ciò significa anche che qualsiasi Observer impostata per il risultato LiveData verrà attivata anche quando le destinazioni delle finestre di dialogo sono ancora sullo schermo. Se vuoi controllare il risultato solo quando la destinazione della finestra di dialogo è chiusa e la destinazione sottostante diventa quella attuale, puoi osservare il valore Lifecycle associato a NavBackStackEntry e recuperare il risultato solo quando diventa RESUMED.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navController = findNavController();
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment)

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains("key")) {
            val result = navBackStackEntry.savedStateHandle.get<String>("key");
            // Do something with the result
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    NavController navController = NavHostFragment.findNavController(this);
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment);

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    final LifecycleEventObserver observer = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_RESUME)
                && navBackStackEntry.getSavedStateHandle().contains("key")) {
                String result = navBackStackEntry.getSavedStateHandle().get("key");
                // Do something with the result
            }
        }
    };
    navBackStackEntry.getLifecycle().addObserver(observer);

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_DESTROY)) {
                navBackStackEntry.getLifecycle().removeObserver(observer)
            }
        }
    });
}

Lo stack di navigazione posteriore archivia un elemento NavBackStackEntry non solo per ogni singola destinazione, ma anche per ogni grafico di navigazione padre che contiene la singola destinazione. Ciò consente di recuperare un elemento NavBackStackEntry che ha come ambito un grafico di navigazione. Un NavBackStackEntry con ambito grafico di navigazione consente di creare un elemento ViewModel con ambito di grafico di navigazione, in modo da condividere dati relativi all'interfaccia utente tra le destinazioni del grafico. Tutti gli oggetti ViewModel creati in questo modo rimangono attivi finché l'elemento NavHost associato e i relativi ViewModelStore non vengono cancellati o fino a quando il grafico di navigazione non viene rimosso dallo stack precedente.

L'esempio seguente mostra come recuperare un ViewModel che ha come ambito un grafico di navigazione:

Kotlin

val viewModel: MyViewModel
        by navGraphViewModels(R.id.my_graph)

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);

Se utilizzi Navigazione 2.2.0 o precedente, devi indicare i dati di fabbrica in modo da utilizzare lo stato salvato con ViewModels, come illustrato nell'esempio seguente:

Kotlin

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) {
    SavedStateViewModelFactory(requireActivity().application, requireParentFragment())
}

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

ViewModelProvider viewModelProvider = new ViewModelProvider(
        backStackEntry.getViewModelStore(),
        new SavedStateViewModelFactory(
                requireActivity().getApplication(), requireParentFragment()));

MyViewModel myViewModel = provider.get(myViewModel.getClass());

Per ulteriori informazioni su ViewModel, consulta Panoramica del modello ViewModel.

Modificare grafici di navigazione gonfiati

Puoi modificare dinamicamente un grafico di navigazione mostrato in modo artificioso durante l'esecuzione.

Ad esempio, se hai un elemento BottomNavigationView associato a un NavGraph, la destinazione predefinita di NavGraph determina la scheda selezionata all'avvio dell'app. Tuttavia, potrebbe essere necessario ignorare questo comportamento, ad esempio quando una preferenza dell'utente specifica una scheda preferita da caricare all'avvio dell'app. In alternativa, l'app potrebbe dover modificare la scheda iniziale in base al comportamento passato degli utenti. Puoi supportare questi casi specificando in modo dinamico la destinazione predefinita di NavGraph.

Prendi in considerazione questo NavGraph:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home">
    <fragment
        android:id="@+id/home"
        android:name="com.example.android.navigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/location"
        android:name="com.example.android.navigation.LocationFragment"
        android:label="fragment_location"
        tools:layout="@layout/fragment_location" />
    <fragment
        android:id="@+id/shop"
        android:name="com.example.android.navigation.ShopFragment"
        android:label="fragment_shop"
        tools:layout="@layout/fragment_shop" />
    <fragment
        android:id="@+id/settings"
        android:name="com.example.android.navigation.SettingsFragment"
        android:label="fragment_settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

Quando questo grafico viene caricato, l'attributo app:startDestination specifica che HomeFragment deve essere visualizzato. Per eseguire l'override dinamico della destinazione iniziale:

  1. Innanzitutto, gonfia manualmente la NavGraph.
  2. Esegui l'override della destinazione di partenza.
  3. Infine, allega manualmente il grafico a NavController.

Kotlin

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

val navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph)
navGraph.startDestination = R.id.shop
navController.graph = navGraph
binding.bottomNavView.setupWithNavController(navController)

Java

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
        .findFragmentById(R.id.nav_host_fragment);

NavController navController = navHostFragment.getNavController();
NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph);
navGraph.setStartDestination(R.id.shop);
navController.setGraph(navGraph);
NavigationUI.setupWithNavController(binding.bottomNavView, navController);

Ora, quando si avvia l'app, viene mostrata l'app ShopFragment anziché HomeFragment.

Quando utilizzi i link diretti, l'elemento NavController crea automaticamente uno stack di ritorno per la destinazione del link diretto. Se l'utente va al link diretto e poi torna indietro, raggiungerà la destinazione iniziale a un certo punto. Se esegui l'override della destinazione iniziale utilizzando la tecnica dell'esempio precedente, viene aggiunta la destinazione iniziale corretta allo stack di backup creato.

Tieni presente che questa tecnica consente anche di eseguire l'override di altri aspetti di NavGraph, come richiesto. Tutte le modifiche al grafico devono essere apportate prima della chiamata a setGraph() per garantire che venga utilizzata la struttura corretta per la gestione dei link diretti, il ripristino dello stato e lo spostamento alla destinazione iniziale del grafico.