Interagire in modo programmatico con il componente Navigazione

Il componente Navigazione offre vari modi per creare e interagire in modo programmatico con alcuni elementi di navigazione.

Crea un NavHostFragment

Puoi utilizzare NavHostFragment.create() per creare in modo programmatico un elemento NavHostFragment con una specifica risorsa di grafico, come mostrato 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 a NavHost intercetta le pressioni del pulsante Indietro di sistema. Puoi implementare questo comportamento anche il tuo XML NavHost aggiungendo app:defaultNavHost="true". Se implementi comportamento del pulsante Indietro personalizzato e non vuoi che NavHost intercetta la pressione del pulsante Indietro, puoi passare Da null a setPrimaryNavigationFragment().

A partire da Navigazione 2.2.0, puoi ottenere un riferimento NavBackStackEntry per qualsiasi destinazione nello stack di navigazione, NavController.getBackStackEntry(), passando un ID destinazione. Se lo stack posteriore contiene più di un'istanza della destinazione specificata, getBackStackEntry() restituisce l'istanza di livello più alto. dall'elenco.

Il valore NavBackStackEntry restituito fornisce un'istanza Lifecycle, un ViewModelStore e un SavedStateRegistry a livello di destinazione. Questi oggetti sono validi per tutta la durata della destinazione nel back stack. Quando la destinazione associata viene separata dalla back stack, 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 che funzionano con stato salvato a prescindere dal tipo di destinazione che utilizzi. Ciò è particolarmente utile quando si lavora tipi di destinazione a cui non è associato automaticamente un valore Lifecycle, ad esempio le destinazioni personalizzate.

Ad esempio, puoi osservare il Lifecycle di un NavBackStackEntry proprio come osserveresti il valore Lifecycle di un frammento o un'attività. Inoltre, NavBackStackEntry è un LifecycleOwner, il che significa che puoi utilizzarlo quando osservando 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(). Stati del ciclo di vita per le destinazioni che non si trovano in cima al back stack sposta da RESUMED a STARTED se le destinazioni sono ancora visibili sotto un Destinazione FloatingWindow, ad esempio una destinazione della finestra di dialogo, o a STOPPED negli altri casi.

Restituzione di un risultato alla destinazione precedente

Nella Navigazione 2.3 e successive, NavBackStackEntry consente l'accesso a un SavedStateHandle Un SavedStateHandle è una mappa chiave-valore che può essere utilizzata per archiviare e recuperare e i dati di Google Cloud. Questi valori persistono anche attraverso la morte del processo, inclusa la configurazione modifiche e rimangono disponibili tramite lo stesso oggetto. Utilizzando il modello SavedStateHandle, puoi accedere ai dati e trasmetterli tra le destinazioni. Ciò è particolarmente utile come meccanismo per recuperare i dati da un dopo che è stata rimossa dalla pila.

Per ritrasmettere i dati alla Destinazione A dalla Destinazione B, devi prima configura la destinazione A per ascoltare un risultato sul relativo SavedStateHandle. A questo scopo, recupera NavBackStackEntry utilizzando il metodo API getCurrentBackStackEntry() e quindi observe il LiveData fornita 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 nella SavedStateHandle di Destinazione A mediante 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 richiamare remove() su SavedStateHandle per cancellare il risultato. Se non rimuovi risultato, LiveData continuerà a restituire l'ultimo risultato a qualsiasi nuove istanze Observer.

Considerazioni sull'utilizzo delle destinazioni delle finestre di dialogo

Quando navigate per raggiungere una destinazione che offre la visualizzazione completa del NavHost (ad es. una destinazione <fragment>), la destinazione precedente il ciclo di vita è stato interrotto, impedendo qualsiasi callback al LiveData fornita da SavedStateHandle.

Tuttavia, quando accedi a destinazione finestra di dialogo, la destinazione precedente è visibile sullo schermo ed è quindi anche STARTED pur non essendo la destinazione corrente. Ciò significa che le chiamate getCurrentBackStackEntry() da metodi del ciclo di vita come onViewCreated() restituirà il NavBackStackEntry della destinazione della finestra di dialogo dopo una modifica della configurazione o un processo di decesso e ricreazione (dalla finestra di dialogo viene ripristinata al di sopra dell'altra destinazione). Pertanto, devi utilizzare getBackStackEntry() con l'ID della destinazione, per assicurarti di utilizzare sempre NavBackStackEntry.

Ciò significa anche che qualsiasi Observer impostata sul risultato LiveData verrà 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 destinazione sottostante diventa la destinazione corrente, puoi osservare Lifecycle associato a NavBackStackEntry e recupera 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 a ritroso consente di archiviare un NavBackStackEntry non solo per ogni singola destinazione, ma anche per ogni navigazione padre che contiene la singola destinazione. Ciò ti consente di recuperare un NavBackStackEntry con ambito a un grafico di navigazione. Un sistema di navigazione L'elemento NavBackStackEntry con ambito grafico consente di creare un ViewModel con ambito a un grafico di navigazione, che ti consente di condividere i dati relativi all'interfaccia utente tra destinazioni del grafico. Tutti gli oggetti ViewModel creati in questo modo rimangono attivi fino alla l'elemento NavHost associato e i relativi ViewModelStore vengono cancellati o fino a quando il grafico di navigazione è saltato fuori dallo stack posteriore.

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 Navigatore 2.2.0 o versioni precedenti, devi fornire fabbrica per utilizzare Stato salvato con ViewModels come mostrato 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, vedi Panoramica del ViewModel.

Modifica di grafici di navigazione gonfiati

Puoi modificare dinamicamente un grafico di navigazione gonfiato al momento dell'attivazione.

Ad esempio, se disponi di una BottomNavigationView associato a un NavGraph, la destinazione predefinita del NavGraph indica la scheda selezionata all'avvio dell'app. Tuttavia, Bisogna ignorare questo comportamento, ad esempio quando una preferenza dell'utente specifica una scheda preferita da caricare all'avvio dell'app. In alternativa, la tua app devi modificare la scheda iniziale in base al comportamento passato degli utenti. Puoi a questi casi specificando in modo dinamico la destinazione predefinita NavGraph.

Considera 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 della destinazione di partenza in modo dinamico, procedi nel seguente modo:

  1. Innanzitutto, gonfia manualmente NavGraph.
  2. Sostituisci la destinazione di partenza.
  3. Infine, collega 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, all'avvio dell'app, viene visualizzata la dicitura ShopFragment anziché HomeFragment.

Quando vengono utilizzati i link diretti, NavController crea un back stack automaticamente per la destinazione del link diretto. Se l'utente accede al link diretto, poi torna indietro, raggiunge la destinazione a un certo punto. Se esegui l'override della destinazione di partenza utilizzando dell'esempio precedente, garantisce che l'inizio corretto viene aggiunta allo stack esistente creato.

Tieni presente che questa tecnica consente anche di ignorare altri aspetti della NavGraph in base alle esigenze. Tutte le modifiche al grafico devono essere apportate prima della chiamata a setGraph() per verificare che la struttura corretta viene utilizzato per la gestione dei link diretti, il ripristino dello stato e lo spostamento all'inizio destinazione del grafico.