Jak wyczyścić stos nawigacji po przejściu do innego fragmentu w systemie Android

122

Używam nowego komponentu architektury nawigacji w systemie Android i utknąłem w czyszczeniu stosu nawigacji po przejściu do nowego fragmentu.

Przykład: Jestem w loginFragment i chcę, aby ten fragment został usunięty ze stosu, kiedy przejdę do fragmentu głównego, aby użytkownik nie powrócił do loginFragmentu po naciśnięciu przycisku Wstecz.

Do nawigacji używam prostego NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) .

Aktualny kod:

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

Próbowałem użyć NavOptions w nawigacji () , ale przycisk Wstecz nadal odsyła mnie z powrotem do 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);
Youssef El Behi
źródło
Można używać popBackStacklub nie dodawać LoginFragmentdo backstack dostarczyć nulldo addToBackStack(null);i zastąpić ją nowąFragment
YUPI
Edytuj swój post i dodaj kod, którego używasz teraz do nawigacji
Stodoły
Zredagowałem moje pytanie i dodałem kod
Youssef El Behi
Myślę, że @Yupi dostarczył dobrą sugestię. Lub możesz użyć navigate()metody takiej jak navigate(int resId, Bundle args, NavOptions navOptions)i zapewnić to, NavOptionsco najlepiej pasuje do twojego senario
Barns
Próbowałem użyć NavOptions, ale przycisk Wstecz nadal odsyła mnie z powrotem do logowaniaFragment
Youssef El Behi

Odpowiedzi:

181

Najpierw dodaj atrybuty app:popUpTo='your_nav_graph_id'i app:popUpToInclusive="true"do tagu akcji.

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

Po drugie, przejdź do celu, używając powyższej czynności jako parametru.

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

Więcej informacji można znaleźć w dokumentacji .

UWAGA : Jeśli nawigujesz za pomocą metody navigate(@IdRes int resId), nie uzyskasz pożądanego rezultatu. Dlatego użyłem metody navigate(@NonNull NavDirections directions).

Subhojit Shaw
źródło
1
Najlepsza odpowiedź jako clearTask jest teraz przestarzała w nowych wersjach komponentu nawigacyjnego Androida!
Michał Ziobro
14
Można też użyć identyfikatora akcji w ten sposóbfindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin
1
Niesamowite! Jednak teraz mam glif kopii zapasowej / wstecz, ale naciśnięcie go nic nie robi. Jaki jest czysty sposób na usunięcie tego też?
Danish Khan
6
Myślę, że kluczem do tej odpowiedzi jest to, że "popUpTo" (które usuwa wszystkie fragmenty między twoim aktualnym fragmentem a pierwszym wystąpieniem identyfikatora, który przekazujesz do tego argumentu) jest ustawione na id samego wykresu nawigacyjnego . Skutecznie całkowicie usuwa backstack. Dodanie tego komentarza tylko dlatego, że nie widziałem, aby był dokładnie wyjaśniony w ten sposób i właśnie tego szukałem. +1!
Scott O'Toole
7
UWAGA : Nie to, że klasa „Wskazówki dojazdu” nie zostanie wygenerowana, jeśli nie dołączysz gradle safe-args. Aby uzyskać więcej informacji, odwiedź tutaj
Jaydip Kalkani
28

Myślę, że twoje pytanie dotyczy konkretnie sposobu korzystania z funkcji Pop Behavior / Pop To / app: popUpTo (w xml)

W dokumentacji
wyskakuj do określonego celu przed rozpoczęciem nawigacji. Spowoduje to zdejmowanie wszystkich niepasujących miejsc docelowych z tylnego stosu, aż do znalezienia tego miejsca docelowego.

Przykład (aplikacja do prostego poszukiwania pracy)
mój wykres start_screen_nav wygląda następująco:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

jeśli chcę przejść do EmployerMainFragmenti wyświetlić wszystko, w tym, startScreenFragment kod będzie wyglądał następująco:

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

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

jeśli chcę przejść do EmployerMainFragmenti wyświetlić wszystkie z wyłączeniem, startScreenFragment kod będzie wyglądał następująco:

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

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

jeśli chcę przejść do EmployerMainFragmenti wyskoczyć, loginFragmentale nie, startScreenFragment kod będzie wyglądał następująco:

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

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

LUB

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

            app:popUpTo="@+id/startScreenFragment"/>
dongkichan
źródło
Jak to zrobić programowo zamiast w XML?
IgorGanapolsky
28

W moim przypadku musiałem usunąć wszystko z tylnego stosu, zanim otworzyłem nowy fragment, więc użyłem tego kodu

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

pierwsza linia usuwa tylny stos, dopóki nie osiągnie fragmentu określonego w moim przypadku, jest to fragment domowy, więc całkowicie usuwa cały tylny stos, a kiedy użytkownik kliknie z powrotem we fragment_company, zamyka aplikację.

lamba
źródło
1
Co to jest fragment_apps??
IgorGanapolsky
1
Obecny fragment. To tak, jakbyś był na fragmentOne i chcesz przejść do fragmentu.Dwa też musisz użyć navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni
14

UWAGA: Wyczyść zadanie jest przestarzałe, oficjalny opis już

Ta metoda jest przestarzała. Użyj setPopUpTo (int, boolean) z identyfikatorem wykresu NavControllera i ustaw włącznie na true.

Stara odpowiedź

Jeśli nie chce przejść przez cały ten fuzz w kodzie, można po prostu sprawdzić Clear Taskw Launch Optionswe właściwościach działania.

Opcje uruchamiania

Edycja: Począwszy od Android Studio 3.2 Beta 5, Wyczyść zadanie nie jest już widoczne w oknie Opcje uruchamiania, ale nadal można go używać w kodzie XML nawigacji, w tagu akcji , dodając

app:clearTask="true"
Mel
źródło
Zamiast tego należy użyć identyfikatora katalogu głównego wykresu, tak jak w komunikacie clearTask xml atrybuteeprecation: „ustaw popUpTo na początek wykresu i użyj popUpToInclusive”. Zrobiłem to w ten sposób i osiągnąłem to samo pożądane zachowanie za pomocą findNavController (). Navigate (@IdRes resId, bundle)
artnest
Używanie zalecanego podejścia do ustawiania popUpTo i popUpToInclusive nie wydaje się działać w przypadku działań między miejscami docelowymi innymi niż miejsce docelowe: issuetracker.google.com/issues/116831650
hsson
W rzeczywistości napotykam problem, który nie działa z miejscem docelowym początkowym, co jest frustrujące.
Mel
Jak byś to zrobił, jeśli nie znasz fragmentu, do którego chcesz się dostać? Na przykład mam fragment, który tworzy nowy przedmiot. Po utworzeniu chcę pokazać szczegółowy widok tego przedmiotu. Ale nie chcę, aby użytkownik później wracał do utworzonego fragmentu, ale do poprzedniego widoku. Może to być lista wszystkich pozycji lub inna pozycja, na której oparto nową. Czy zatem clearTask jest jedyną opcją?
findusl
Nie, właściwie nie powinno się go już używać. Zamiast tego powinieneś używać popUpTo włącznie. Wkrótce zaktualizuję odpowiedź o moje ustalenia dotyczące użycia popUpTo.
Mel
5

W końcu to rozgryzłem dzięki Jak wyłączyć UP w nawigacji dla jakiegoś fragmentu za pomocą nowego komponentu architektury nawigacji?

Musiałem określić .setClearTask (true) jako 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());

                    }

                }
            });
Youssef El Behi
źródło
2
Cieszę się, że mogłeś zrealizować moją sugestię użycia navigate(int resId, Bundle args, NavOptions navOptions)iNavOptions
Stodoły
Tak. Dzięki @Barns
Youssef El Behi
14
tak, „setClearTask” jest przestarzałe, ale zamiast tego możesz użyć: 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 powinno działać. Prawdopodobnie używasz niewłaściwego docelowego fragmentId (np. R.id.my_next_fragMoże to być login lub ekran powitalny); także jeśli chcesz popBack tylko jeden, można użyć: NavHostFragment.findNavController(this).popBackStack().
Pablo Reyes
2
@PabloReyes To jest poprawna odpowiedź. Po prostu określ, który fragment zostanie usunięty jako ostatni: Powiedz, że masz ten stos: Fragment_1 Fragment_2 Fragment_3 Jesteś teraz na Fragment_3 i chcesz przejść do Fragment_4 i wyczyścić wszystkie inne fragmenty. Po prostu określ w nawigacji xml: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413
5

Oto, jak to robię.

 //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)
Abdul Rahman Shamair
źródło
Dlaczego masz flagę inkluzywnąfalse ?
IgorGanapolsky
2
Zgodnie z dokumentacją inclusive boolean: true to also pop the given destination from the back stack Ustawiłem więc włączanie na false, ponieważ nie chcę zdejmować bieżącego fragmentu ze stosu.
Abdul Rahman Shamair
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);
    }
emad pirayesh
źródło
1

Możesz zastąpić naciśnięcie wstecz działania podstawowego w następujący sposób:

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

}
ROHIT LIEN
źródło
1

Uwaga: to może nie odnosić się do rzeczywistego pytania.

To podejście będzie przydatne, jeśli do utrzymania w nawigacji potrzebna jest pojedyncza instancja fragmentu .

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

Tutaj navOptionsjest DSL i należy go zaimportować. W chwili udzielania tej odpowiedzi używam następujących wersji interfejsu nawigacji w 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"
Sagar Chapagain
źródło
0

Przez chwilę walczyłem, aby przycisk Wstecz nie wrócił do mojego fragmentu początkowego, który w moim przypadku był wiadomością wprowadzającą, która powinna pojawić się tylko raz.

Prostym rozwiązaniem było stworzenie globalnej akcji wskazującej miejsce docelowe, w którym powinien pozostać użytkownik. Musisz ustawić app:popUpTo="..."poprawnie - ustaw go na miejsce, w którym chcesz zostać wyskakujący. W moim przypadku była to moja wiadomość wprowadzająca. Ustaw równieżapp:popUpToInclusive="true"

Tyler
źródło
0

Mogę to wyjaśnić na małym przykładzie. Mam dwa fragmenty listy pracowników i usuwanie pracowników. W przypadku usuwania pracownika mam dwa zdarzenia kliknięcia: anuluj i usuń. Jeśli kliknę Anuluj, po prostu wrócę do ekranu listy pracowników, a jeśli kliknę Usuń, chcę wyczyścić wszystkie poprzednie backstacks i przejść do ekranu listy pracowników.

Aby przejść do poprzedniego ekranu:

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


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

Aby wyczyścić wszystkie poprzednie backstack i przejść do nowego ekranu:

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

Więcej informacji: Przykład nawigacji Jetpack

Dhrumil Shah
źródło
0

Możesz zrobić tak proste, jak:

getFragmentManager().popBackStack();

Jeśli chcesz sprawdzić licznik, możesz:

getFragmentManager().getBackStackEntryCount()
Pan T.
źródło