Як очистити навігаційний стек після переходу до іншого фрагмента в Android


121

Я використовую новий компонент навігаційної архітектури в 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що найкраще відповідає вашому сенаріо
Барнс

Я спробував використовувати The NavOptions, але кнопка "назад" все одно повертає мене до логіну. Фрагмент
Youssef El Behi

Відповіді:


179

Спочатку додайте атрибути 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!
Michał Ziobro

14
Можна також використати ідентифікатор дії на зразок такfindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Калін

1
Дивовижно! Однак зараз у мене є гліф резервного / резервного копіювання, але натискання на нього нічого не робить. Який чистий спосіб видалити це теж?
Датський хан

6
Я думаю, що ключ до цієї відповіді полягає в тому, що "popUpTo" (який видаляє всі фрагменти між вашим поточним фрагментом і першим появою ідентифікатора, який ви передаєте цьому аргументу) встановлюється на ідентифікатор самого навігаційного графіка . Ефективно повністю очищаючи рюкзак. Лише додавши цей коментар, оскільки я не бачив, щоб це було пояснено саме так, і саме це я шукав. +1!
Скотт О'Тул

7
ПРИМІТКА : Не те, що клас "Вказівки" не буде генеровано, якщо ви не включили безпечний аргумент gradle. Для отримання додаткової інформації відвідайте тут
Jaydip Kalkani

28

Я думаю, що ваше питання стосується конкретного використання Поп-поведінки / 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 і хочете перейти фрагментTwo вам також потрібно використовувати navController.popBackStack (R.id.fragmentOne, правда); navController.navigate (R.id.fragmentTwo);
Ббені

14

ПРИМІТКА: Очищення завдання застаріле, офіційний опис є

Цей метод застарілий. Використовуйте setPopUpTo (int, boolean) з ідентифікатором графіка NavController і встановіть включно на true.

Стара відповідь

Якщо ви не хочете пройти через весь цей пух в коді, ви можете просто перевірити Clear Taskв Launch Optionsу властивостях дії.

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

Редагувати: Станом на Android Studio 3.2 Beta 5, Clear Task більше не відображається у вікні параметрів запуску, але ви все одно можете використовувати його у XML-коді навігації, у тезі дії , додавши

app:clearTask="true"

Ви повинні використовувати ідентифікатор кореня графіка замість того, що в тексті clearTask xml attributedeprecation написано: "встановіть popUpTo до кореня вашого графіка та використовуйте popUpToInclusive". Я зробив це так і домігся такої ж бажаної поведінки з findNavController (). Навігація (@IdRes resId, bundle)
artnest

Використовуючи рекомендований підхід до налаштування popUpTo та popUpToInclusive, схоже, не працює для дій між пунктами призначення, які не є початковим пунктом призначення: issueetracker.google.com/isissue/116831650
hsson

Насправді я стикаюся з проблемою, коли вона не працює з початковим пунктом призначення, розчаровує.
Мел

Як би ти це зробив, якщо ти не знаєш фрагмента, до якого хочеш потрапити? Наприклад, у мене є фрагмент, який створює новий елемент. Після створення я хочу показати детальний вид цього предмета. Але я не хочу, щоб користувач після цього повертався до фрагмента створення, а не до попереднього. Це може бути перелік усіх предметів або інший предмет, на якому базувався новий. Тоді 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

Так. Дякуємо @Barns
Юссеф Ель Бехі

14
так, "setClearTask" застаріло, але замість цього ви можете використовувати: val navOptions = NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build() findNavController().navigate(R.id.my_next_frag, null, navOptions)
Pablo Reyes

1
@ c-an це має працювати. Можливо, ви використовуєте неправильний цільовий fragmentId (наприклад, це 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" додаток: popUpToInclusive = "вірно"
користувач3193413

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 Отже, я встановив включення на значення 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
                            })

Ось, navOptionsє DSL і їх потрібно імпортувати. Під час цієї відповіді я використовую наступні версії gradle Navigation UI .

    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.