Programmatisch mit der Navigationskomponente interagieren

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.

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)
            }
        }
    });
}

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:

  1. Blasen Sie zuerst NavGraph manuell auf.
  2. Startziel überschreiben.
  3. 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.