Как программно изменить начальную точку навигационного графа [Jetpack]

В принципе, у меня есть такой навигационный график:

введите описание изображения здесь

Я хочу изменить начальную точку в навигационном графике на fragment 2 сразу после его достижения (чтобы не возвращаться к fragment 1 при нажатии кнопки «Назад» - как на экране-заставке).

Это мой код:

navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);

Но, очевидно, что это не работает, и он возвращается к fragment 1 после нажатия кнопки возврата.

Я что делаю неправильно? Есть ли другое решение?


person Shynline    schedule 04.07.2018    source источник
comment
Чего вы хотите добиться после нажатия кнопки возврата на фрагменте 2?   -  person Alexey Denysenko    schedule 04.07.2018
comment
в этом случае я хочу выйти из приложения   -  person Shynline    schedule 04.07.2018
comment
Можно программно изменить начальную точку в навигации. график. см. здесь, чтобы получить решение.   -  person Akash Patel    schedule 30.08.2018
comment
Я даже не знал, что вы можете установить график программно, спасибо, что этот вопрос мне очень помог!   -  person dave o grady    schedule 04.03.2020
comment
Не уверен, почему ваш код не работает. Я устанавливаю его в onCreate и без проблем. Ваш код в вашем вопросе решил мою проблему :) thnx!   -  person Boy    schedule 19.02.2021
comment
Код в вопросе отлично работает на этапе настройки, то есть в методе onCreate () Activity, который содержит NavHostFragment. Раньше я предполагал, что setStartDestination () будет достаточно, потому что он работает с исходным экземпляром NavGraph. Но, очевидно, только вызов setGraph () распространяет изменение.   -  person Peter F    schedule 05.03.2021


Ответы (7)


ОБНОВЛЕНИЕ:

Когда у вас есть такой навигационный график:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

И вы хотите перейти ко второму фрагменту и сделать его корнем вашего графа, укажите следующий NavOptions:

NavOptions navOptions = new NavOptions.Builder()
        .setPopUpTo(R.id.firstFragment, true)
        .build();

И используйте их для навигации:

Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions);

setPopUpTo(int destinationId, boolean inclusive) - всплывающее окно к заданному пункту назначения перед навигацией. Это выталкивает все несовпадающие места назначения из заднего стека, пока это место назначения не будет найдено.

destinationId - Пункт назначения, к которому следует всплывать, очищая все промежуточные пункты назначения.

inclusive - истина, чтобы также извлечь указанное место назначения из заднего стека.


АЛЬТЕРНАТИВА:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
<action
    android:id="@+id/action_firstFragment_to_secondFragment"
    app:destination="@id/secondFragment"
    app:popUpTo="@+id/firstFragment"
    app:popUpToInclusive="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

А затем в вашем коде:

findNavController(fragment).navigate(
    FirstFragmentDirections.actionFirstFragmentToSecondFragment())

Старый ответ

Устарело: Атрибут clearTask для действий и связанный API в NavOptions устарел.

Источник: https://developer.android.com/jetpack/docs/release-notes


Если вы хотите изменить корневой фрагмент на fragment 2 (например, после нажатия кнопки возврата на fragment 2 вы выйдете из приложения), вы должны поместить следующий атрибут в свой action или destination:

app:clearTask="true"

Практически это выглядит следующим образом:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment"
    android:label="fragment_first" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:clearTask="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"
    android:label="fragment_second"/>

Я добавил app:clearTask="true" в действие.


Теперь при переходе от fragment 1 к fragment 2 используйте следующий код:

Navigation.findNavController(view)
        .navigate(R.id.action_firstFragment_to_secondFragment);
person Alexey Denysenko    schedule 04.07.2018
comment
Это НЕ работает для меня. Куда вы кладете этот код? Как и у вас, у меня есть приветственный фрагмент, который установлен как домашний. В методе onActivityCreated приветственного фрагмента я получаю свою модель просмотра, проверяю, вошел ли пользователь в систему, и если да, то я попробовал ваше решение сверху. У меня все еще есть стрелка вверх на 2-м (новом доме) фрагменте. Как мне от этого избавиться? - person szaske; 26.10.2018
comment
@szaske, пожалуйста, создайте еще один вопрос и предоставьте свой исходный код, чтобы я смог вам помочь - person Alexey Denysenko; 27.10.2018
comment
Я запутался. Я отправил свое решение ниже со своим кодом. Насколько я понимаю, вы ответили на свой собственный вопрос о происхождении, но у меня была такая же проблема, как и у вас, и ваше решение только частично решило мою проблему. Считается ли дурным тоном предлагать другой подход? - person szaske; 28.10.2018
comment
Это работает, проголосовано за, но я обнаружил, что на самом деле это нарушает обратную функциональность, оставляя пустой navHostFragment в действии, если, например, ваш firstFragment использовался при запуске навигации, есть ли элегантный способ закрыть действие контейнера с помощью этого? - person desgraci; 17.05.2019
comment
@desgraci, вы используете setPopUpTo() с true в качестве второго аргумента? - person Alexey Denysenko; 17.05.2019
comment
@AlexeyDenysenko да сэр! firstFragment это мой startDestination Я выполняю навигацию по secondFragment, как предложено, и когда я снова нажимаю secondFragment, действие не завершается, нет специальной конфигурации для его воспроизведения, и код сведен к минимуму, чтобы избежать других флагов, влияющих на навигацию - person desgraci; 17.05.2019
comment
@desgraci хорошо, я проверю текущее поведение и дам вам ответ - person Alexey Denysenko; 17.05.2019
comment
@AlexeyDenysenko Заранее спасибо! - person desgraci; 17.05.2019
comment
имейте в виду, что если вы попытаетесь выполнить findNavController.popBackstack из Fragment2 в попытке перейти к Fragment1, это не будет работать таким образом, если вы реализовали решение выше. Вышеупомянутое решение будет работать только для ручного обратного пресса. popBackStack вернет false, и вы застрянете на Fragment2. - person Muhammad Ahmed AbuTalib; 20.07.2019
comment
@MuhammadAhmedAbuTalib сказал, что вы правы. В этом сценарии, как вы исправили такое поведение? - person Archie G. Quiñones; 26.09.2019

Я нашел решение для этого, но оно некрасивое. Думаю, этого следовало ожидать с альфа-библиотекой, но я надеюсь, что Google постарается упростить / исправить это, поскольку это довольно популярный шаблон навигации.

Решение Алексея у меня не сработало. Моя проблема заключалась в том, что на моей панели действий отображались стрелки вверх, используя:

NavigationUI.setupActionBarWithNavController(this, navController)

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

Вот код, необходимый для того, чтобы получить то, что я хотел:

  • Фрагмент №1 - это то место, где мое приложение изначально запускается
  • Я могу выполнить проверку аутентификации во фрагменте №1, а затем программно изменить начало на фрагмент №2.
  • Во фрагменте № 2 стрелка вверх отсутствует, и нажатие кнопки «Назад» не приведет вас к фрагменту № 1.

Вот код, который это делает. В моей деятельности onCreate:

// Setup the toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(false)

// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController
        .navInflater.inflate(R.navigation.nav_graph)
graph.startDestination = R.id.welcomeFragment

// This seems to be a magical command. Not sure why it's needed :(
navHost.navController.graph = graph

NavigationUI.setupActionBarWithNavController(this, navHost.navController)

а также:

fun makeHomeStart(){
    graph.startDestination = R.id.homeFragment
}

Затем во фрагменте # 1 onActivityCreated по предложению Алексея:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    ...

    // Check for user authentication
    if(sharedViewModel.isUserAuthenticated()) {

        (activity as MainActivity).makeHomeStart() //<---- THIS is the key
        val navOptions = NavOptions.Builder()
                .setPopUpTo(R.id.welcomeFragment, true)
                .build()
        navController.navigate(R.id.action_welcomeFragment_to_homeFragment,null,navOptions)

    } else {
        navController.navigate(R.id.action_welcomeFragment_to_loginFragment)
    }
}

Ключевой код: (activity as MainActivity).makeHomeStart(), который просто запускает метод в действии, который изменяет startDestination графиков. Я мог бы очистить это и превратить в интерфейс, но я подожду Google и надеюсь, что они улучшат весь этот процесс. Мне кажется, что метод setPopUpTo плохо назван, и интуитивно не понятно, как вы называете фрагмент, который вырезается из графика. Мне также странно, что они вносят эти изменения в navOptions. Я бы подумал, что navOptions будет относиться только к действию навигации, к которому они подключены.

И я даже не знаю, что делает navHost.navController.graph = graph, но без этого стрелки вверх возвращаются. :(

Я использую Navigation 1.0.0-alpha06.

person szaske    schedule 27.10.2018
comment
Я не уверен, почему за вас проголосовали, поскольку это довольно полезный ответ, и он решает проблему OP. Необходимо выполнить странное назначение графа, поскольку вы увеличиваете фрагмент навигации напрямую, а не с помощью обычного механизма findNavController. Я обновил ваш пост, добавив в него вариант вместо того, чтобы размещать альтернативный ответ. - person Carel; 07.04.2019
comment
Спасибо за ваш ответ. Есть ли сейчас лучшее решение? - person Francis; 15.02.2020

Вы также можете попробовать следующее.

val navController = findNavController(R.id.nav_host_fragment)
if (condition) {
    navController.setGraph(R.navigation.nav_graph_first)
} else {
    navController.setGraph(R.navigation.nav_graph_second)
}

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

person Doctiger    schedule 12.12.2019
comment
есть какое-либо представление об этой демонстрации, я сделал это, но не знаю, что это за обработчик, это с bottomnavigationview и viewpager github.com/sunil-singh-chaudhary/ - person Sunil Chaudhary; 20.08.2020
comment
@SunilChaudhary Перейдите по этой ссылке, в ней есть руководство по обновлению пользовательского интерфейса. developer.android.com/guide/navigation/navigation-ui, но в В вашем случае настройка viewpager с помощью BottomNavigationView не является чем-то для компонента навигации, я думаю. - person Doctiger; 20.08.2020

В MainActivity.kt

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val inflater = navHostFragment.navController.navInflater
    val graph = inflater.inflate(R.navigation.booking_navigation)

    if (isTrue){
        graph.startDestination = R.id.DetailsFragment
    }else {
        graph.startDestination = R.id.OtherDetailsFragment
    }

    val navController = navHostFragment.navController
    navController.setGraph(graph, intent.extras)

Удалите startDestination из nav_graph.xml

?xml version="1.0" encoding="utf-8"?>
<!-- app:startDestination="@id/oneFragment" -->

<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/navigation_main">

  <fragment
    android:id="@+id/DetailFragment"
    android:name="DetailFragment"
    android:label="fragment_detail"
    tools:layout="@layout/fragment_detail"/>
  <fragment
    android:id="@+id/OtherDetailFragment"
    android:name="OtherDetailFragment"
    android:label="fragment_other_detail"
    tools:layout="@layout/fragment_other_detail"/>
</navigation>
person Dino Sunny    schedule 17.07.2020

На самом деле вам не нужно выталкивать фрагмент всплеска. Он может оставаться там до конца жизни вашего приложения. Что вам нужно сделать, так это на начальном экране определить, какой следующий экран показывать.

Логин State Machine Flow

На картинке выше вы можете спросить в состоянии экрана-заставки, есть ли сохраненный LoginToken. Если он пуст, вы переходите к экрану входа в систему.

После завершения экрана входа вы анализируете результат, сохраняете токен и переходите на домашний экран следующего фрагмента.

Когда на главном экране нажата кнопка «Назад», вы отправляете обратно на экран-заставку сообщение с результатом, указывающее на завершение работы приложения.

Нижний код может помочь:

val nextDestination = if (loginSuccess) {
    R.id.action_Dashboard
} else {
    R.id.action_NotAuthorized
}

val options = NavOptions.Builder()
    .setPopUpTo(R.id.loginParentFragment, true)
    .build()

findNavController().navigate(nextDestination, null, options)
person Pablo Valdes    schedule 18.10.2019

Хорошо, немного поработав с этим, я нашел решение, которое сработало для меня и не потребовало тонны работы.

Похоже, две вещи ДОЛЖНЫ быть на месте, чтобы он работал так, как если бы ваш второй фрагмент был вашим начальным пунктом назначения.

используйте АЛЬТЕРНАТИВНУЮ опцию в принятом сообщении

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >

    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:popUpTo="@+id/firstFragment"
        app:popUpToInclusive="true" /> 

</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

Вышеупомянутое приведет к удалению firstFragment из стека и раздуванию secondFragment при перемещении. Приложение больше не может вернуться к firstFragment, НО вы слева с secondFragment, показывающим стрелку назад, как заявил @szaske.

Вот что имело значение. Я ранее определил свой AppBarConfig, используя NavigationController.graph, вот так

// Old code
val controller by lazy { findNavController(R.id.nav_host_fragment) }
val appBarConfig by lazy { AppBarConfiguration(controller.graph) }

Обновление для определения набора мест назначения верхнего уровня устранило проблему отображения стрелки назад на secondFragment вместо значка меню гамбургера.

// secondFragment will now show hamburger menu instead of back arrow.
val appBarConfig by lazy { AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment)) }

Установка начального пункта назначения может иметь или не иметь негативных последствий для вашего проекта, поэтому делайте это по мере необходимости, однако в этом примере нам это не нужно. Если вам становится тепло и нечетко, чтобы убедиться, что на вашем графике определен правильный начальный фрагмент, вы можете сделать это так.

controller.graph.startDestination = R.id.secondFragment

Примечание. Установка этого параметра не предотвращает появление стрелки назад в secondFragment, и, судя по тому, что я обнаружил, похоже, не влияет на навигацию.

person DevinM    schedule 21.08.2020
comment
Когда / где вы используете контроллер и appBarConfig? - person pokumars; 23.04.2021
comment
@pokumars Вы должны сделать это в своей деятельности, когда настраиваете панель действий setupActionBarWithNavController (navController, appBarConfig) - person DevinM; 04.05.2021

Для тех, у кого есть xml-файл навигации с содержанием, аналогичным этому:

<?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/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_users"
        android:name="UsersFragment"
        android:label="@string/users"
        tools:layout="@layout/fragment_users" />

    <fragment
        android:id="@+id/nav_settings"
        android:name="SettingsFragment"
        android:label="@string/settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

Предположим, что текущий открытый фрагмент является домашним фрагментом, и вы хотите перейти к фрагменту пользователей, для этого просто вызовите setOnClickListener элемента, который вы хотите перейти к методу навигации из контроллера навигации, аналогичного этому коду:

yourElement.setOnClickListener {
  view.findNavController().navigate(R.id.nav_users)
}

это заставит приложение перейти к этому другому фрагменту, а также обработает заголовок на панели инструментов.

person Juan Ricardo    schedule 01.11.2020