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()
.
Riferimento a una destinazione utilizzando NavBackStackEntry
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) } } }); }
Condividi i dati relativi alla UI tra le destinazioni con ViewModel
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:
- Innanzitutto, gonfia manualmente la
NavGraph
. - Esegui l'override della destinazione di partenza.
- 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.