Korzystanie z komponentu Nawigacja w sposób programowy

Komponent Nawigacja umożliwia programowe tworzenie i interakcję z określonymi elementami nawigacyjnymi.

Tworzenie fragmentu NavHostFragment

Za pomocą NavHostFragment.create() aby automatycznie utworzyć NavHostFragment z określonym zasobem grafu, jak w tym przykładzie:

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

Pamiętaj, że setPrimaryNavigationFragment(finalHost) umożliwia: NavHost przechwytywania naciśnięć przycisku Wstecz w systemie. Możesz również zastosować to działanie w: NavHost, dodając app:defaultNavHost="true". Jeśli implementujesz niestandardowe zachowanie przycisku Wstecz i nie chcesz, aby urządzenie NavHost przechwytowało naciśnięcia przycisku Wstecz, null do setPrimaryNavigationFragment().

Począwszy od Nawigacji w wersji 2.2.0, możesz mieć dostęp do NavBackStackEntry dla dowolnego miejsca docelowego ze stosu nawigacji, wywołując NavController.getBackStackEntry(), i przekazujemy mu identyfikator miejsca docelowego. Jeśli stos tylny zawiera więcej niż jedną instancję wskazanego miejsca docelowego, getBackStackEntry() zwraca instancję najwyższego poziomu z grupy.

Zwrócona wartość NavBackStackEntry zapewnia Lifecycle ViewModelStore oraz SavedStateRegistry na poziomie miejsca docelowego. Te obiekty są ważne przez cały okres istnienia miejsca docelowego na stosie wstecznym. Gdy powiązane miejsce docelowe zostanie wydzielone z stos wsteczny, obiekt Lifecycle zostaje zniszczony, stan nie jest już zapisywany, a wszystkie obiekty ViewModel zostały wyczyszczone.

Te właściwości zapewniają Lifecycle oraz magazyn obiektów ViewModel i zajęcia, z którymi stan zapisany niezależnie rodzaj miejsca docelowego, z którego korzystasz. Jest to szczególnie przydatne podczas pracy z typy miejsc docelowych, które nie mają automatycznie powiązanego atrybutu Lifecycle, takich jak niestandardowe miejsca docelowe.

Na przykład możesz obserwować Lifecycle elementu NavBackStackEntry tak samo można obserwować Lifecycle fragmentu lub aktywności. Ponadto NavBackStackEntry to LifecycleOwner, co oznacza, że możesz go używać, gdy obserwując wartość LiveData lub inne komponenty uwzględniające cykl życia, co pokazuje następujący przykład:

Kotlin

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

Java

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

Stan cyklu życia jest automatycznie aktualizowany za każdym razem, gdy wywołujesz funkcję navigate(). Stany cyklu życia dla miejsc docelowych, które nie znajdują się na górze stosu tylnego przejdź z RESUMED do STARTED, jeśli miejsca docelowe są nadal widoczne w miejsce docelowe FloatingWindow, takie jak miejsce docelowe w oknie dialogowym, lub do STOPPED w przeciwnym razie.

Zwracanie wyniku do poprzedniego miejsca docelowego

W Nawigacji w wersji 2.3 i nowszych NavBackStackEntry umożliwia dostęp do funkcji SavedStateHandle SavedStateHandle to mapa klucz-wartość, której można używać do przechowywania i pobierania danych i skalowalnych danych. Te wartości są utrwalone na etapie zakończenia procesu, w tym konfiguracji zmian i pozostają dostępne w ramach tego samego obiektu. Korzystając z parametru SavedStateHandle, możesz uzyskiwać dostęp do danych i przekazywać je między miejscami docelowymi. Jest to szczególnie przydatne jako mechanizm odzyskiwania danych z miejsce docelowe po jego wyrzuceniu ze stosu.

Aby przekazać dane z miejsca docelowego A z powrotem do miejsca docelowego B, najpierw skonfigurować miejsce docelowe A, aby nasłuchiwać wyniku na jego urządzeniu SavedStateHandle. Aby to zrobić, pobierz NavBackStackEntry za pomocą polecenia getCurrentBackStackEntry() API, a następnie observe LiveData źródło: 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.
        }
    });
}

W miejscu docelowym B musisz set wynik w SavedStateHandle Miejsce docelowe A za pomocą interfejsu getPreviousBackStackEntry() API.

Kotlin

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

Java

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

Jeśli chcesz przetworzyć wynik tylko raz, musisz wywołać remove() na urządzeniu SavedStateHandle, aby wyczyścić wynik. Jeśli nie usuniesz wynik, LiveData będzie nadal zwracać ostatni wynik wszystkim nowych instancji (Observer).

O czym warto pamiętać podczas korzystania z miejsc docelowych okien

Gdy navigate dotrze do miejsca docelowego z pełnym widokiem NavHost (np. <fragment>) – poprzedni cel podróży został zatrzymany cykl życia, co zapobiega wywoływaniu przez nie wywołań zwrotnych do LiveData źródło: SavedStateHandle.

Po przejściu do dialog destination, poprzedni cel podróży jest również widoczny na ekranie i dlatego jest także STARTED, mimo że nie jest to obecny cel podróży. Oznacza to, że wywołania getCurrentBackStackEntry() z metod cyklu życia, takich jak onViewCreated() zwraca wartość NavBackStackEntry adresu docelowego okna. po zmianie konfiguracji albo procesie śmierci lub rekreacji (od nad innym miejscem docelowym). Dlatego getBackStackEntry() za pomocą identyfikatora miejsca docelowego, aby zawsze używać prawidłowego NavBackStackEntry

Oznacza to również, że każdy element Observer ustawiony w wyniku LiveData zostanie jest wyzwalana, nawet gdy na ekranie wciąż znajdują się miejsca docelowe okien. Jeśli chcesz sprawdzać wynik tylko wtedy, gdy miejsce docelowe w oknie jest zamknięte, a przycisk bazowe miejsce docelowe stanie się bieżącym miejscem docelowym, możesz obserwować Powiązanie Lifecycle z obiektem NavBackStackEntry i pobieranie wyniku tylko wtedy, gdy zmieni się on na 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)
            }
        }
    });
}

Stos nawigacji wstecz zawiera NavBackStackEntry nie tylko w przypadku poszczególnych miejsc docelowych, ale również dla poszczególnych elementów nawigacji który zawiera konkretne miejsce docelowe. Dzięki temu możesz pobrać NavBackStackEntry, którego zakres jest ograniczony do wykresu nawigacyjnego. Nawigacja funkcji NavBackStackEntry o zakresie na wykresie pozwala utworzyć obiekt ViewModel, który ograniczone do wykresu nawigacyjnego, co pozwala udostępniać dane dotyczące interfejsu miejsc docelowych wykresu. Wszystkie utworzone w ten sposób obiekty ViewModel są aktywne do powiązane pole NavHost i jego ViewModelStore są wyczyszczone lub do momentu jest wyskakujący ze stosu tylnego.

Poniższy przykład pokazuje, jak pobrać obiekt ViewModel ograniczony do zakresu wykres nawigacyjny:

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

Jeśli korzystasz z Nawigacji w wersji 2.2.0 lub starszej, musisz podać własną do użycia w wersji fabrycznej Saved State with ViewModels (Zapisany stan za pomocą modeli widoków), jak w tym przykładzie:

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

Więcej informacji o ViewModel: Przegląd modelu widoku.

Modyfikowanie powiększonych wykresów nawigacyjnych

Wykres nawigacyjny możesz modyfikować dynamicznie w czasie działania.

Na przykład, jeśli masz BottomNavigationView powiązany z miejscem docelowym NavGraph, domyślnym miejscem docelowym NavGraph dyktuje wybraną kartę podczas uruchamiania aplikacji. Możesz jednak muszą zastąpić to zachowanie, na przykład gdy preferencje użytkownika określają preferowanej karty, która ma być ładowana po uruchomieniu aplikacji. Aplikacja może też zmienić kartę początkową na podstawie wcześniejszych działań użytkownika. Dostępne opcje obsługuje takie przypadki, dynamicznie określając domyślne miejsce docelowe NavGraph.

Weź pod uwagę 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>

Gdy ten wykres zostanie wczytany, atrybut app:startDestination określa, że HomeFragment ma być wyświetlany. Zastąpienie miejsca docelowego początkowego wykonaj te czynności:

  1. Najpierw ręcznie rozszerzaj NavGraph.
  2. Zastąp miejsce docelowe początkowego.
  3. Na koniec ręcznie dołącz wykres do tabeli 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);

Od teraz po uruchomieniu aplikacji zamiast HomeFragment pojawi się ShopFragment.

Gdy używasz precyzyjnych linków, NavController tworzy stos wsteczny dla miejsca docelowego precyzyjnego linku. Jeśli użytkownik przechodzi do precyzyjnego linku, a następnie przechodzi wstecz, dociera do miejsca docelowego. Zastąpienie miejsca docelowego początkowego za pomocą funkcji w poprzednim przykładzie zapewnia, że właściwy początek miejsce docelowe jest dodawane do utworzonego stosu wstecznego.

Ta technika pozwala także zastąpić inne aspekty NavGraph zgodnie z wymaganiami. Należy wprowadzić wszystkie zmiany w wykresie przed wywołaniem funkcji setGraph(), aby zapewnić prawidłową strukturę jest używany do obsługi precyzyjnych linków, przywracania stanu i przechodzenia na początek miejsce docelowe wykresu.