Компонент Navigation предоставляет способы программного создания и взаимодействия с определенными элементами навигации.
Создание NavHostFragment
Вы можете использовать NavHostFragment.create(), чтобы программно создать NavHostFragment с определенным ресурсом графа, как показано в примере ниже.
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();
Обратите внимание, что setPrimaryNavigationFragment(finalHost) позволяет NavHost перехватывать нажатия системной кнопки "Назад". Вы также можете реализовать это поведение в XML-файле NavHost, добавив app:defaultNavHost="true". Если вы реализуете собственное поведение кнопки "Назад" и не хотите, чтобы NavHost перехватывал нажатия кнопки "Назад", вы можете передать null в setPrimaryNavigationFragment().
Ссылка на пункт назначения с помощью NavBackStackEntry
Начиная с версии Navigation 2.2.0, вы можете получить ссылку на NavBackStackEntry для любого пункта назначения в стеке навигации, вызвав NavController.getBackStackEntry() и передав ему идентификатор пункта назначения. Если в обратном стеке содержится несколько экземпляров указанного пункта назначения, getBackStackEntry() возвращает самый верхний экземпляр из стека.
Возвращаемый объект NavBackStackEntry предоставляет Lifecycle, ViewModelStore и SavedStateRegistry на уровне пункта назначения. Эти объекты действительны в течение всего времени существования пункта назначения в обратном стеке. Когда связанный пункт назначения удаляется из обратного стека, Lifecycle уничтожается, состояние больше не сохраняется, а все объекты ViewModel очищаются.
Эти свойства предоставляют вам Lifecycle и хранилище для объектов и классов ViewModel, которые работают с сохраненным состоянием независимо от того, какой тип пункта назначения вы используете. Это особенно полезно при работе с типами целевых страниц, которые не имеют автоматически связанного Lifecycle, например с пользовательскими целевыми страницами.
Например, вы можете наблюдать за Lifecycle NavBackStackEntry так же, как за Lifecycle фрагмента или объекта activity. Кроме того, NavBackStackEntry – это LifecycleOwner, поэтому его можно использовать при наблюдении за LiveData или с другими компонентами, учитывающими жизненный цикл, как показано в следующем примере:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
Состояние жизненного цикла автоматически обновляется при каждом вызове navigate().
Состояния жизненного цикла для пунктов назначения, которые не находятся вверху обратного стека, перемещаются из RESUMED в STARTED, если пункты назначения по-прежнему видны под пунктом назначения FloatingWindow, например пунктом назначения диалогового окна, или в STOPPED в противном случае.
Возврат результата в предыдущий пункт назначения
В Navigation 2.3 и более поздних версиях NavBackStackEntry предоставляет доступ к SavedStateHandle.
SavedStateHandle – это карта ключ-значение, которую можно использовать для хранения и извлечения данных. Эти значения сохраняются при завершении процесса, в том числе при изменении конфигурации, и остаются доступными через тот же объект. Используя заданный SavedStateHandle, вы можете получать доступ к данным и передавать их между целевыми сервисами.
Это особенно полезно в качестве механизма для возврата данных из пункта назначения после того, как он будет извлечен из стека.
Чтобы передать данные обратно из пункта назначения B в пункт назначения A, сначала настройте пункт назначения A на прослушивание результата в его SavedStateHandle.
Для этого получите NavBackStackEntry с помощью getCurrentBackStackEntry() API, а затем observe LiveData, предоставленный 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. } }); }
В пункте назначения Б вы должны set результат на SavedStateHandle пункта назначения А, используя getPreviousBackStackEntry() API.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
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.
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) } } }); }
Как обмениваться данными, связанными с пользовательским интерфейсом, между целевыми страницами с помощью ViewModel
Обратный стек навигации хранит NavBackStackEntry не только для каждого отдельного пункта назначения, но и для каждого родительского графа навигации, который содержит этот пункт назначения. Это позволяет получить NavBackStackEntry, область действия которой ограничена графом навигации. NavBackStackEntry в области графа навигации позволяет создать ViewModel в области графа навигации, что позволяет обмениваться данными, связанными с пользовательским интерфейсом, между пунктами назначения графа. Любые объекты ViewModel, созданные таким образом, существуют до тех пор, пока не будут очищены связанные с ними NavHost и ViewModelStore или пока граф навигации не будет извлечен из обратного стека.
В следующем примере показано, как получить ViewModel, область действия которого ограничена графом навигации:
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);
Если вы используете Navigation 2.2.0 или более ранней версии, вам нужно предоставить собственную фабрику, чтобы использовать Saved State с ViewModels, как показано в примере ниже.
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());
Подробную информацию о 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. Чтобы динамически переопределить начальный пункт назначения, выполните следующие действия:
- Сначала надуйте
NavGraphвручную. - Переопределить начальный пункт назначения.
- Наконец, вручную прикрепите график к
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);
Теперь при запуске приложения вместо HomeFragment будет показываться ShopFragment.
При использовании ссылок на контент NavController автоматически создает обратный стек для целевой страницы. Если пользователь переходит по ссылке на контент, а затем возвращается назад, в какой-то момент он достигнет начального пункта назначения. Переопределение начального пункта назначения с помощью метода из предыдущего примера гарантирует, что в созданный обратный стек будет добавлен правильный начальный пункт назначения.
Обратите внимание, что этот метод также позволяет переопределять другие аспекты NavGraph по мере необходимости. Все изменения в графе должны быть сделаны до вызова setGraph(), чтобы при обработке ссылок на контент, восстановлении состояния и переходе к начальному пункту назначения графа использовалась правильная структура.