Программное взаимодействие с компонентом навигации,Взаимодействие программным способом с компонентом навигации.

Компонент «Навигация» предоставляет способы программного создания и взаимодействия с определенными элементами навигации.

Создайте NavHostFragment.

Вы можете использовать NavHostFragment.create() для программного создания NavHostFragment с определенным ресурсом графа, как показано в примере ниже:

Котлин

val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit()

Ява

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit();

Обратите внимание, что setPrimaryNavigationFragment(finalHost) позволяет вашей системе NavHost перехватывать нажатия кнопки «Назад». Вы также можете реализовать это поведение в своем XML-коде NavHost добавив app:defaultNavHost="true" . Если вы реализуете собственное поведение кнопки «Назад» и не хотите, чтобы NavHost перехватывал нажатия кнопки «Назад», вы можете передать null в setPrimaryNavigationFragment() .

Начиная с версии Navigation 2.2.0, вы можете получить ссылку на NavBackStackEntry для любого пункта назначения в стеке навигации, вызвав NavController.getBackStackEntry() , передав ему идентификатор пункта назначения. Если задний стек содержит более одного экземпляра указанного пункта назначения, getBackStackEntry() возвращает самый верхний экземпляр из стека.

Возвращенный NavBackStackEntry предоставляет Lifecycle , ViewModelStore и SavedStateRegistry на уровне назначения. Эти объекты действительны в течение всего времени существования пункта назначения в заднем стеке. Когда связанный пункт назначения извлекается из заднего стека, Lifecycle уничтожается, состояние больше не сохраняется, а все объекты ViewModel очищаются.

Эти свойства предоставляют вам Lifecycle и хранилище для объектов и классов ViewModel , которые работают с сохраненным состоянием независимо от того, какой тип места назначения вы используете. Это особенно полезно при работе с типами мест назначения, которые автоматически не имеют связанного Lifecycle , например с настраиваемыми местами назначения.

Например, вы можете наблюдать за Lifecycle NavBackStackEntry так же, как если бы вы наблюдали за Lifecycle фрагмента или действия. Кроме того, NavBackStackEntry является LifecycleOwner , что означает, что вы можете использовать его при наблюдении LiveData или с другими компонентами, поддерживающими жизненный цикл, как показано в следующем примере:

Котлин

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

Ява

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

Состояние жизненного цикла автоматически обновляется всякий раз, когда вы вызываете navigate() . Состояния жизненного цикла для целевых объектов, которые не находятся в верхней части заднего стека, переходят из RESUMED в STARTED , если места назначения все еще видны под местом назначения FloatingWindow , например, в месте назначения диалога, или в состояние STOPPED в противном случае.

Возврат результата в предыдущий пункт назначения

В навигации 2.3 и более поздних версиях NavBackStackEntry предоставляет доступ к SavedStateHandle . SavedStateHandle — это карта значений ключа, которую можно использовать для хранения и извлечения данных. Эти значения сохраняются даже после смерти процесса, включая изменения конфигурации, и остаются доступными через тот же объект. Используя данный SavedStateHandle , вы можете получать доступ к данным и передавать их между пунктами назначения. Это особенно полезно в качестве механизма возврата данных из места назначения после их извлечения из стека.

Чтобы передать данные обратно в пункт назначения A из пункта назначения B, сначала настройте пункт назначения A для прослушивания результата в его SavedStateHandle . Для этого извлеките NavBackStackEntry с помощью API getCurrentBackStackEntry() , а затем observe LiveData , предоставленную SavedStateHandle .

Котлин

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.
    }
}

Ява

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

В пункте назначения B вы должны set результат в SavedStateHandle пункта назначения A с помощью API getPreviousBackStackEntry() .

Котлин

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

Ява

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

Если вы хотите обработать результат только один раз, вам необходимо вызвать remove() для SavedStateHandle , чтобы очистить результат. Если вы не удалите результат, LiveData продолжит возвращать последний результат всем новым экземплярам Observer .

Рекомендации по использованию пунктов назначения диалога

Когда вы navigate к месту назначения, которое обеспечивает полное представление NavHost (например, к месту назначения <fragment> ), жизненный цикл предыдущего пункта назначения останавливается, что предотвращает любые обратные вызовы к LiveData , предоставленным SavedStateHandle .

Однако при переходе к пункту назначения диалога предыдущий пункт назначения также виден на экране и, следовательно, также STARTED несмотря на то, что он не является текущим пунктом назначения. Это означает, что вызовы getCurrentBackStackEntry() из методов жизненного цикла, таких как onViewCreated() вернут NavBackStackEntry пункта назначения диалога после изменения конфигурации или смерти и восстановления процесса (поскольку диалог восстанавливается выше другого пункта назначения). Поэтому вам следует использовать getBackStackEntry() с идентификатором пункта назначения, чтобы гарантировать, что вы всегда используете правильный NavBackStackEntry .

Это также означает, что любой Observer , который вы установите для результата LiveData будет запущен, даже если пункты назначения диалога все еще находятся на экране. Если вы хотите проверить результат только тогда, когда пункт назначения диалога закрыт и базовый пункт назначения становится текущим пунктом назначения, вы можете наблюдать Lifecycle , связанный с NavBackStackEntry , и получать результат только тогда, когда он становится RESUMED .

Котлин

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

Ява

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

В обратном стеке навигации хранится NavBackStackEntry не только для каждого отдельного пункта назначения, но также для каждого родительского графа навигации, содержащего отдельный пункт назначения. Это позволяет вам получить NavBackStackEntry , область действия которого ограничена графом навигации. NavBackStackEntry в области навигационного графа предоставляет способ создания ViewModel , область действия которого ограничена навигационным графом, что позволяет обмениваться данными, связанными с пользовательским интерфейсом, между пунктами назначения графа. Любые объекты ViewModel , созданные таким образом, живут до тех пор, пока связанный NavHost и его ViewModelStore не будут очищены или пока граф навигации не будет извлечен из заднего стека.

В следующем примере показано, как получить ViewModel , ограниченную графом навигации:

Котлин

val viewModel: MyViewModel
        by navGraphViewModels(R.id.my_graph)

Ява

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);

Если вы используете Navigation 2.2.0 или более раннюю версию, вам необходимо предоставить собственную фабрику для использования Saved State с ViewModels , как показано в следующем примере:

Котлин

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) {
    SavedStateViewModelFactory(requireActivity().application, requireParentFragment())
}

Ява

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

Дополнительные сведения о ViewModel см. в разделе Обзор ViewModel .

Изменение завышенных навигационных графиков

Вы можете динамически изменять расширенный граф навигации во время выполнения.

Например, если у вас есть BottomNavigationView , привязанный к NavGraph , место назначения NavGraph по умолчанию определяет выбранную вкладку при запуске приложения. Однако вам может потребоваться переопределить это поведение, например, когда в настройках пользователя указана предпочтительная вкладка, которая будет загружаться при запуске приложения. Альтернативно вашему приложению может потребоваться изменить стартовую вкладку в зависимости от поведения пользователя в прошлом. Вы можете поддержать эти случаи, динамически указав место назначения NavGraph по умолчанию.

Рассмотрим этот 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>

Когда этот график загружается, атрибут app:startDestination указывает, что HomeFragment должен отображаться. Чтобы динамически переопределить начальный пункт назначения, выполните следующие действия:

  1. Сначала раздуйте NavGraph вручную.
  2. Переопределить начальный пункт назначения.
  3. Наконец, вручную прикрепите график к NavController .

Котлин

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)

Ява

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

Теперь при запуске вашего приложения вместо HomeFragment отображается ShopFragment .

При использовании глубоких ссылок NavController автоматически создает обратный стек для места назначения глубоких ссылок. Если пользователь перейдет по глубокой ссылке, а затем вернется назад, в какой-то момент он достигнет начальной точки назначения. Переопределение начального пункта назначения с использованием метода из предыдущего примера гарантирует, что правильный начальный пункт назначения будет добавлен в построенный обратный стек.

Обратите внимание, что этот метод также позволяет при необходимости переопределять другие аспекты NavGraph . Все изменения в графе должны быть выполнены до вызова setGraph() чтобы гарантировать использование правильной структуры при обработке глубоких ссылок, восстановлении состояния и перемещении к начальному пункту назначения вашего графа.