Die Navigationskomponente bietet die Möglichkeit, bestimmte Navigationselemente programmatisch zu erstellen und mit ihnen zu interagieren.
NavHostFragment erstellen
Sie können NavHostFragment.create()
verwenden, um wie im folgenden Beispiel programmatisch eine NavHostFragment
mit einer bestimmten Grafikressource zu erstellen:
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();
Mit setPrimaryNavigationFragment(finalHost)
kann NavHost
das Drücken der Zurück-Taste des Systems abfangen. Du kannst dieses Verhalten auch in deiner NavHost
-XML implementieren, indem du app:defaultNavHost="true"
hinzufügst. Wenn du ein benutzerdefiniertes Verhalten der Zurück-Schaltfläche implementierst und nicht möchtest, dass NavHost
das Drücken der Zurück-Schaltfläche abfängt, kannst du null
an setPrimaryNavigationFragment()
übergeben.
Mit NavBackStackEntry auf ein Ziel verweisen
Ab Navigation 2.2.0 können Sie für jedes Ziel im Navigationsstapel einen Verweis auf NavBackStackEntry
abrufen. Dazu rufen Sie NavController.getBackStackEntry()
auf und übergeben eine Ziel-ID. Wenn der Back-Stack mehr als eine Instanz des angegebenen Ziels enthält, gibt getBackStackEntry()
die oberste Instanz aus dem Stack zurück.
Der zurückgegebene NavBackStackEntry
enthält einen Lifecycle
, einen ViewModelStore
und einen SavedStateRegistry
auf Zielebene. Diese Objekte sind für die Lebensdauer des Ziels auf dem Back-Stack gültig. Wenn das zugehörige Ziel aus dem Back-Stack entfernt wird, wird das Lifecycle
gelöscht, der Status nicht mehr gespeichert und alle ViewModel
-Objekte werden gelöscht.
Diese Attribute bieten Ihnen ein Lifecycle
und einen Speicher für ViewModel
-Objekte und -Klassen, die unabhängig vom verwendeten Zieltyp mit dem gespeicherten Status funktionieren. Dies ist besonders nützlich, wenn Sie mit Zieltypen arbeiten, denen nicht automatisch eine Lifecycle
zugeordnet ist, z. B. benutzerdefinierte Ziele.
Sie können beispielsweise den Lifecycle
eines NavBackStackEntry
genau wie den Lifecycle
eines Fragments oder einer Aktivität beobachten. Darüber hinaus ist NavBackStackEntry
ein LifecycleOwner
. Das bedeutet, dass Sie es beim Beobachten von LiveData
oder mit anderen Komponenten, die den Lebenszyklus berücksichtigen, verwenden können, wie im folgenden Beispiel gezeigt:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
Der Lebenszyklusstatus wird automatisch aktualisiert, wenn Sie navigate()
aufrufen.
Lebenszyklusstatus für Ziele, die sich nicht oben im Back-Stack befinden, werden von RESUMED
nach STARTED
verschoben, wenn die Ziele noch unter einem FloatingWindow
-Ziel sichtbar sind, z. B. einem Dialogziel, oder nach STOPPED
.
Ergebnis an das vorherige Ziel zurückgeben
In Navigation 2.3 und höher gewährt NavBackStackEntry
Zugriff auf ein SavedStateHandle
.
Ein SavedStateHandle
ist eine Schlüssel/Wert-Zuordnung zum Speichern und Abrufen von Daten. Diese Werte bleiben während des Prozessabschlusses, einschließlich Konfigurationsänderungen, bestehen und bleiben über dasselbe Objekt verfügbar. Mit dem angegebenen SavedStateHandle
können Sie auf Daten zugreifen und diese zwischen Zielen übergeben.
Dies ist besonders nützlich, um Daten von einem Ziel wiederherzustellen, nachdem sie aus dem Stack entfernt wurden.
Damit Daten von Ziel B an Ziel A zurückgegeben werden, richten Sie zuerst Ziel A so ein, dass sein SavedStateHandle
auf ein Ergebnis wartet.
Rufen Sie dazu das NavBackStackEntry
mit der getCurrentBackStackEntry()
API und dann observe
die von SavedStateHandle
bereitgestellte LiveData
ab.
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. } }); }
In Ziel B müssen Sie das Ergebnis für SavedStateHandle
von Ziel A mit der getPreviousBackStackEntry()
API set
.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Wenn Sie ein Ergebnis nur einmal verarbeiten möchten, müssen Sie remove()
für SavedStateHandle
aufrufen, um das Ergebnis zu löschen. Wenn Sie das Ergebnis nicht entfernen, gibt LiveData
weiterhin das letzte Ergebnis an alle neuen Observer
-Instanzen zurück.
Überlegungen bei der Verwendung von Dialogzielen
Wenn Sie navigate
an ein Ziel senden, das die vollständige Ansicht von NavHost
einnimmt (z. B. ein <fragment>
-Ziel), wird der Lebenszyklus des vorherigen Ziels beendet. Dadurch werden Callbacks an das LiveData
verhindert, das von SavedStateHandle
bereitgestellt wird.
Wenn Sie jedoch ein Dialogziel aufrufen, ist auch das vorherige Ziel auf dem Bildschirm sichtbar und lautet daher STARTED
, auch wenn es nicht das aktuelle Ziel ist. Bei getCurrentBackStackEntry()
-Aufrufen innerhalb von Lebenszyklusmethoden wie onViewCreated()
wird also nach einer Konfigurationsänderung oder nach dem Löschen und Neuerstellen eines Prozesses das NavBackStackEntry
des Dialogfeldziels zurückgegeben (da das Dialogfeld über dem anderen Ziel wiederhergestellt wird). Daher sollten Sie getBackStackEntry()
mit der ID des Ziels verwenden, damit immer die richtige NavBackStackEntry
verwendet wird.
Das bedeutet auch, dass jede Observer
, die Sie für das Ergebnis LiveData
festlegen, auch dann ausgelöst wird, wenn die Dialogfeldziele noch auf dem Bildschirm angezeigt werden. Wenn Sie das Ergebnis nur prüfen möchten, wenn das Dialogfeldziel geschlossen ist und das zugrunde liegende Ziel zum aktuellen Ziel wird, können Sie die mit NavBackStackEntry
verknüpfte Lifecycle
beobachten und das Ergebnis erst abrufen, wenn es zu RESUMED
wird.
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) } } }); }
UI-bezogene Daten zwischen Zielen mit ViewModel teilen
Im Back-Stack Navigation wird ein NavBackStackEntry
nicht nur für jedes einzelne Ziel gespeichert, sondern auch für jedes übergeordnete Navigationsdiagramm, das das einzelne Ziel enthält. Auf diese Weise können Sie eine NavBackStackEntry
abrufen, die auf ein Navigationsdiagramm beschränkt ist. Mit einer NavBackStackEntry
auf Navigationsgrafik können Sie eine ViewModel
erstellen, die sich auf ein Navigationsdiagramm bezieht. Damit können Sie UI-bezogene Daten für die Ziele des Diagramms freigeben. Alle auf diese Weise erstellten ViewModel
-Objekte sind aktiv, bis die zugehörige NavHost
und ihre ViewModelStore
gelöscht sind oder die Navigationsgrafik aus dem Back-Stack entfernt wird.
Das folgende Beispiel zeigt, wie Sie eine ViewModel
abrufen, die sich auf ein Navigationsdiagramm bezieht:
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);
Wenn Sie Navigation 2.2.0 oder älter verwenden, müssen Sie Ihre eigene Fabrik bereitstellen, um Saved State with ViewModels wie im folgenden Beispiel zu verwenden:
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());
Weitere Informationen zu ViewModel
finden Sie in der Übersicht zu ViewModel.
Überlastete Navigationsdiagramme ändern
Sie können ein überhöhtes Navigationsdiagramm zur Laufzeit dynamisch ändern.
Wenn beispielsweise ein BottomNavigationView
an eine NavGraph
gebunden ist, bestimmt das Standardziel der NavGraph
den ausgewählten Tab beim Start der App. Möglicherweise müssen Sie dieses Verhalten jedoch überschreiben, z. B. wenn eine Nutzereinstellung einen bevorzugten Tab angibt, der beim Start der Anwendung geladen werden soll. Alternativ muss Ihre Anwendung möglicherweise den Start-Tab basierend auf dem bisherigen Nutzerverhalten ändern. Sie können diese Fälle unterstützen, indem Sie das Standardziel von NavGraph
dynamisch angeben.
Überlegungen zu 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>
Wenn diese Grafik geladen wird, gibt das Attribut app:startDestination
an, dass HomeFragment
angezeigt werden soll. So überschreiben Sie das Startziel dynamisch:
- Blasen Sie zuerst
NavGraph
manuell auf. - Startziel überschreiben.
- Hängen Sie die Grafik schließlich manuell an
NavController
an.
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);
Wenn deine App jetzt startet, wird anstelle von HomeFragment
ShopFragment
angezeigt.
Bei Verwendung von Deeplinks erstellt NavController
automatisch einen Back-Stack für das Ziel des Deeplinks. Wenn der Nutzer zum Deeplink und dann rückwärts navigiert, erreicht er irgendwann das Startziel. Wenn Sie das Startziel mit der Methode im vorherigen Beispiel überschreiben, wird dem erstellten Back-Stack das richtige Startziel hinzugefügt.
Mit diesem Verfahren können bei Bedarf auch andere Aspekte von NavGraph
überschrieben werden. Alle Änderungen an der Grafik müssen vor dem Aufruf von setGraph()
vorgenommen werden, damit bei der Verarbeitung von Deeplinks, dem Wiederherstellen des Status und dem Wechsel zum Startziel der Grafik die richtige Struktur verwendet wird.