Как очистить стек навигации после перехода к другому фрагменту в Android


122

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

Пример: я нахожусь в loginFragment и хочу, чтобы этот фрагмент был очищен из стека, когда я перехожу к домашнему фрагменту, чтобы пользователь не возвращался обратно в loginFragment, когда он нажимает кнопку «Назад».

Я использую простой NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) для навигации.

Текущий код:

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                    }
                }
            });

Я попытался использовать NavOptions в navigate () , но кнопка назад все еще отправляет меня обратно в loginFragment

NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setPopUpTo(R.id.homeFragment, false).build();   
             NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment, null, navOptions);

Вы можете использовать popBackStackили не добавлять LoginFragmentв backstack обеспечить , nullчтобы addToBackStack(null);и заменить его новымFragment
YUPI

Пожалуйста, отредактируйте свое сообщение и добавьте код, который вы сейчас используете для навигации
Barns

Я отредактировал свой вопрос и добавил свой код
Юссеф Эль Бехи

Я думаю, что @Yupi дал хорошее предложение. Или вы можете использовать такой navigate()метод, как navigate(int resId, Bundle args, NavOptions navOptions)и предоставить тот, NavOptionsкоторый лучше всего подходит вашему сенарио
Barns

Я попытался использовать The NavOptions, но кнопка «Назад» все еще возвращает меня к логину Фрагмент
Юсеф Эль Бехи

Ответы:


182

Сначала добавьте атрибуты app:popUpTo='your_nav_graph_id'и app:popUpToInclusive="true"в тег действия.

<fragment
    android:id="@+id/signInFragment"
    android:name="com.glee.incog2.android.fragment.SignInFragment"
    android:label="fragment_sign_in"
    tools:layout="@layout/fragment_sign_in" >
    <action
        android:id="@+id/action_signInFragment_to_usersFragment"
        app:destination="@id/usersFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/main_nav_graph"
        app:popUpToInclusive="true" />
</fragment>

Во-вторых, перейдите к месту назначения, используя указанное выше действие в качестве параметра.

findNavController(fragment).navigate(
     SignInFragmentDirections.actionSignInFragmentToUserNameFragment())

Дополнительную информацию см. В документации .

ПРИМЕЧАНИЕ . Если вы используете метод навигации navigate(@IdRes int resId), вы не получите желаемого результата. Следовательно, я использовал метод navigate(@NonNull NavDirections directions).


1
Лучший ответ, поскольку clearTask теперь устарел в новых версиях Android Navigation Component!
Michał Ziobro

14
Также можно использовать id действия такfindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin

1
Потрясающие! Однако теперь у меня есть глиф резервного / восходящего копирования, но нажатие на него ничего не делает. Какой чистый способ удалить и это?
Danish Khan

6
Я думаю, что ключ к этому ответу заключается в том, что для «popUpTo» (который удаляет все фрагменты между вашим текущим фрагментом и первым вхождением идентификатора, который вы передаете этому аргументу) устанавливается идентификатор самого графа навигации . Фактически полностью очищает бэкстэк. Добавил этот комментарий только потому, что я не видел, чтобы он объяснялся именно так, и это было то, что я искал. +1!
Скотт О'Тул

7
ПРИМЕЧАНИЕ . Не то, чтобы класс «Directions» не был создан, если вы не включили gradle safe-args. Для получения дополнительной информации посетите здесь
Джайдип Калкани

28

Я думаю, ваш вопрос конкретно касается того, как использовать Pop Behavior / Pop To / app: popUpTo (в xml)

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

Пример (простое приложение для поиска вакансий)
мой график start_screen_nav выглядит следующим образом:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

если я хочу перейти EmployerMainFragmentи вывести все, включая, startScreenFragment тогда код будет:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"
            app:popUpToInclusive="true" />

если я хочу перейти EmployerMainFragmentи вывести все, исключая, startScreenFragment тогда код будет:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>

если я хочу перейти EmployerMainFragmentи открыть, loginFragmentно не startScreenFragment тогда, код будет:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/loginFragment"
            app:popUpToInclusive="true"/>

ИЛИ

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>

Как сделать это программно, а не в XML?
Игорь Ганапольский

28

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

navController.popBackStack(R.id.fragment_apps, true);
navController.navigate(R.id.fragment_company);

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


1
Что есть fragment_apps??
Игорь Ганапольский

1
Текущий фрагмент. Это как если вы находитесь на fragmentOne и хотите перейти на fragmentTwo, вам тоже нужно использовать navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni

14

ПРИМЕЧАНИЕ. Очистить задачу устарело, официальное описание

Этот метод устарел. Используйте setPopUpTo (int, boolean) с идентификатором графа NavController и установите для него значение true.

Старый ответ

Если вы не хотите пройти через весь этот пух в коде, вы можете просто проверить Clear Taskв Launch Optionsв свойствах действия.

Параметры запуска

Изменить: начиная с Android Studio 3.2 Beta 5, очистить задачу больше не отображается в окне параметров запуска, но вы все равно можете использовать ее в XML-коде навигации, в теге действия , добавив

app:clearTask="true"

Вместо этого следует использовать идентификатор корня графа, как говорится в сообщении об отмене атрибута clearTask xml: «установите popUpTo в корень графа и используйте popUpToInclusive». Я сделал это вот так и добился того же желаемого поведения с помощью findNavController (). Navigate (@IdRes resId, bundle)
artnest

Использование рекомендованного подхода установки popUpTo и popUpToInclusive, похоже, не работает для действий между пунктами назначения, отличными от исходного
hsson

Я действительно сталкиваюсь с проблемой, когда это не работает с пунктом отправления, разочарование.
Mel

Как бы вы это сделали, если не знаете фрагмент, к которому хотите перейти? Например, у меня есть фрагмент, который создает новый элемент. После создания я хочу показать подробную информацию об этом элементе. Но я не хочу, чтобы пользователь после этого возвращался к фрагменту создания, а к предыдущему представлению. Это может быть список всех элементов или другой элемент, на котором основан новый. Тогда является ли clearTask единственным вариантом?
findusl

Нет, на самом деле его больше не следует использовать. Вместо этого вы должны использовать popUpTo включительно. Я очень скоро обновлю ответ своими выводами об использовании popUpTo.
Мел

5

Я наконец понял это благодаря Как отключить UP в навигации для некоторого фрагмента с новым компонентом архитектуры навигации?

Мне пришлось указать .setClearTask (true) в качестве NavOption.

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "signInWithCredential:success");


                        NavOptions.Builder navBuilder = new NavOptions.Builder();
                        NavOptions navOptions = navBuilder.setClearTask(true).build();
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment,null,navOptions);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());

                    }

                }
            });

2
Рад видеть , что вы были в состоянии реализовать свое предложение использовать navigate(int resId, Bundle args, NavOptions navOptions)иNavOptions
Сараи

Да. Спасибо @Barns
Юсеф Эль Бехи

14
да "setClearTask" устарел, но вместо этого вы можете использовать: val navOptions = NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build() findNavController().navigate(R.id.my_next_frag, null, navOptions)
Пабло Рейес

1
@ c-an должно работать. Вероятно, вы используете неправильный целевой фрагмент (например, это R.id.my_next_fragможет быть как логин или заставка); Кроме того, если вы хотите popBack только один, вы можете использовать: NavHostFragment.findNavController(this).popBackStack().
Пабло Рейес

2
@PabloReyes Это правильный ответ. Просто укажите, какой фрагмент удаляется последним: Допустим, у вас есть этот стек: Fragment_1 Fragment_2 Fragment_3 Теперь вы находитесь на Fragment_3 и хотите перейти к Fragment_4 и очистить все остальные фрагменты. Просто укажите в навигации xml: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413

5

Вот как я это делаю.

 //here the R.id refer to the fragment one wants to pop back once pressed back from the newly  navigated fragment
 val navOption = NavOptions.Builder().setPopUpTo(R.id.startScorecardFragment, false).build()

//now how to navigate to new fragment
Navigation.findNavController(this, R.id.my_nav_host_fragment)
                    .navigate(R.id.instoredBestPractice, null, navOption)

Почему ваш инклюзивный флаг false?
Игорь Ганапольский

2
Согласно документации inclusive boolean: true to also pop the given destination from the back stack Итак, я установил для параметра inclusive значение false, потому что я не хочу извлекать текущий фрагмент из стека.
Абдул Рахман Шамаир,

4
    NavController navController 
    =Navigation.findNavController(requireActivity(),          
    R.id.nav_host_fragment);// initialize navcontroller

    if (navController.getCurrentDestination().getId() == 
     R.id.my_current_frag) //for avoid crash
  {
    NavDirections action = 
    DailyInfoSelectorFragmentDirections.actionGoToDestionationFragment();

    //for clear current fragment from stack
    NavOptions options = new 
    NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build();
    navController.navigate(action, options);
    }

1

Вы можете переопределить нажатие на спину базового действия следующим образом:

override fun onBackPressed() {

  val navigationController = nav_host_fragment.findNavController()
  if (navigationController.currentDestination?.id == R.id.firstFragment) {
    finish()
  } else if (navigationController.currentDestination?.id == R.id.secondFragment) {
    // do nothing
  } else {
    super.onBackPressed()
  }

}

1

Примечание: это может не относиться к самому вопросу.

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

// imports
import androidx.navigation.navOptions

// navigate to fragment
navController.navigate(R.id.nav_single_instance_fragment, null,
                            navOptions {
                                popUpTo = R.id.nav_single_instance_fragment
                                launchSingleTop = true
                            })

Вот navOptionsDSL, и его нужно импортировать. На момент ответа я использую следующие версии Gradle интерфейса навигации .

    def nav_version = "2.2.0"
    implementation "androidx.navigation:navigation-ui:$nav_version"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

0

Некоторое время я изо всех сил пытался предотвратить возврат кнопки «Назад» к моему начальному фрагменту, который в моем случае был вводным сообщением, которое должно появиться только один раз.

Простым решением было создание глобального действия, указывающего на пункт назначения, в котором должен оставаться пользователь. Вы должны установить app:popUpTo="..."правильно - установить его в пункт назначения, который вы хотите получить. В моем случае это было мое вступительное сообщение. Также установитеapp:popUpToInclusive="true"


0

Я могу объяснить это на небольшом примере. У меня есть два фрагмента списка сотрудников и удаление сотрудника. При удалении сотрудника у меня есть два события щелчка, отмена и удаление. Если я нажму «Отмена», он просто вернется к экрану списка сотрудников, а если я нажму «Удалить», я хочу очистить все предыдущие бэкстэки и перейти на экран списка сотрудников.

Для перехода к предыдущему экрану:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"/>


    btn_cancel.setOnClickListener {
                it.findNavController().popBackStack()
            }

Чтобы очистить все предыдущие бэкстэки и перейти на новый экран:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"
        app:popUpTo="@id/employeesListFragment"
        app:popUpToInclusive="true"
        app:launchSingleTop="true" />


 btn_submit.setOnClickListener {
                   it.findNavController().navigate(DeleteEmployeeFragmentDirections.actionDeleteEmployeeFragmentToEmployeesListFragment2())
        }

Для получения дополнительной информации: Пример навигации Jetpack.


0

Вы можете сделать так просто:

getFragmentManager().popBackStack();

Если вы хотите проверить счет, вы можете сделать следующее:

getFragmentManager().getBackStackEntryCount()
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.