Come cancellare lo Stack di navigazione dopo essere passati a un altro frammento in Android


121

Sto usando il nuovo componente dell'architettura di navigazione in Android e sono bloccato nel cancellare lo stack di navigazione dopo essere passato a un nuovo frammento.

Esempio: sono nel loginFragment e desidero che questo frammento venga cancellato dallo stack quando passo al frammento home in modo che l'utente non venga restituito al loginFragment quando preme il pulsante Indietro.

Sto usando un semplice NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) per navigare.

Codice corrente:

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

Ho provato a utilizzare le NavOptions in navigate () , ma il pulsante Indietro continua a rimandarmi al 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);

È possibile utilizzare popBackStacko non aggiungere LoginFragmental backstack fornire nulla addToBackStack(null);e sostituirlo con il nuovoFragment
Yupi

Modifica il tuo messaggio e aggiungi il codice che stai utilizzando per navigare
Barns

Ho modificato la mia domanda e aggiunto il mio codice
Youssef El Behi

Penso che @Yupi abbia fornito un buon suggerimento. Oppure potresti usare il navigate()metodo come navigate(int resId, Bundle args, NavOptions navOptions)e fornire quello NavOptionsche meglio si adatta al tuo senario
Barns

Ho provato a utilizzare The NavOptions ma il pulsante Indietro mi sta ancora rimandando al loginFragment
Youssef El Behi

Risposte:


179

Innanzitutto, aggiungi attributi app:popUpTo='your_nav_graph_id'e app:popUpToInclusive="true"al tag di azione.

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

In secondo luogo, naviga verso la destinazione, utilizzando l'azione sopra come parametro.

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

Vedere la documentazione per ulteriori informazioni.

NOTA : se navighi usando il metodo navigate(@IdRes int resId), non otterrai il risultato desiderato. Quindi, ho usato il metodo navigate(@NonNull NavDirections directions).


1
La migliore risposta come clearTask è ora deprecata nelle nuove versioni del componente di navigazione Android!
Michał Ziobro

14
Si può anche usare l'id dell'azione in questo modofindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin

1
Eccezionale! Tuttavia, ora ho il glifo di backup / su, ma premendolo non fa nulla. Qual è un modo pulito per rimuovere anche quello?
Danese Khan

6
Penso che la chiave di questa risposta risieda nel fatto che "popUpTo" (che rimuove tutti i frammenti tra il frammento corrente e la prima occorrenza dell'id che si passa a questo argomento) è impostato sull'id del grafico di navigazione stesso . Eliminare completamente il backstack in modo efficace. Aggiungendo questo commento solo perché non l'ho visto spiegato esattamente in questo modo, ed era quello che stavo cercando. +1!
Scott O'Toole

7
NOTA : Non quella classe "Directions" non verrà generata se non hai incluso safe-args gradle. Per maggiori informazioni visita qui
Jaydip Kalkani

28

Nel mio caso avevo bisogno di rimuovere tutto nella pila posteriore prima di aprire un nuovo frammento, quindi ho usato questo codice

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

la prima riga rimuove lo stack indietro fino a raggiungere il frammento specificato nel mio caso è il frammento home, quindi rimuove completamente tutto lo stack indietro e quando l'utente fa clic di nuovo in fragment_company chiude l'app.


1
Che cos'è fragment_apps??
IgorGanapolsky

1
Il frammento attuale. È come se tu fossi su fragmentOne e volessi usare fragmentTwo anche tu devi usare navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni

26

Penso che la tua domanda riguardi specificamente come utilizzare Pop Behavior / Pop To / app: popUpTo (in xml)

Nella documentazione,
visualizza una data destinazione prima della navigazione. In questo modo vengono visualizzate tutte le destinazioni non corrispondenti dallo stack posteriore fino a quando non viene trovata questa destinazione.

Esempio (app semplice per la ricerca di lavoro) il
mio grafico start_screen_nav è così:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

se voglio navigare EmployerMainFragmente visualizzare tutto incluso, startScreenFragment il codice sarà:

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

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

se voglio navigare EmployerMainFragmente far apparire tutto escluso, startScreenFragment il codice sarà:

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

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

se voglio navigare EmployerMainFragmente visualizzare loginFragmentma non startScreenFragment il codice sarà:

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

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

O

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

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

Come farlo a livello di codice invece che in XML?
IgorGanapolsky

14

NOTA: Clear task è deprecato, la descrizione ufficiale lo è

Questo metodo è deprecato. Usa setPopUpTo (int, boolean) con l'id del grafico del NavController e imposta inclusive su true.

Vecchia risposta

Se non voglio andare attraverso tutto ciò che fuzz nel codice, si può semplicemente verificare Clear Taskin Launch Optionsnelle proprietà dell'azione.

Opzioni di avvio

Modifica: a partire da Android Studio 3.2 Beta 5, Cancella attività non è più visibile nella finestra Opzioni di avvio, ma puoi comunque utilizzarlo nel codice XML di navigazione, nel tag di azione , aggiungendo

app:clearTask="true"

Dovresti invece usare l'id della radice del grafico come dice il messaggio clearTask xml attributedeprecation: "imposta popUpTo alla radice del tuo grafico e usa popUpToInclusive". L'ho fatto in questo modo e ho ottenuto lo stesso comportamento desiderato con findNavController (). Navigate (@IdRes resId, bundle)
artnest

L'uso dell'approccio consigliato di impostare popUpTo e popUpToInclusive non sembra funzionare per azioni tra destinazioni diverse dalla destinazione di partenza: issuetracker.google.com/issues/116831650
hsson

In realtà sto riscontrando un problema in cui non funziona con la destinazione iniziale, frustrante.
Mel

Come faresti questo se non conosci il frammento in cui desideri inserire? Ad esempio, ho un frammento che crea un nuovo oggetto. Dopo la creazione voglio mostrare la visualizzazione dei dettagli di questo elemento. Ma non voglio che l'utente ritorni al frammento di creazione in seguito, ma alla vista precedente. Può trattarsi di un elenco di tutti gli elementi o di un elemento diverso su cui era basato il nuovo. ClearTask è quindi l'unica opzione?
findusl

No, in realtà non dovrebbe più essere usato. Invece dovresti usare popUpTo in modo inclusivo. Aggiornerò la risposta molto presto con i miei risultati sull'utilizzo di popUpTo.
Mel

5

Finalmente lo capisco grazie a Come disabilitare UP in Navigation per alcuni frammenti con il nuovo componente di architettura di navigazione?

Ho dovuto specificare .setClearTask (true) come 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
Sono contento di vedere che sei stato in grado di attuare il mio suggerimento di utilizzare navigate(int resId, Bundle args, NavOptions navOptions)eNavOptions
Barns

Sì. Grazie @Barns
Youssef El Behi

14
sì, "setClearTask" è deprecato ma invece puoi usare: 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 dovrebbe funzionare. Probabilmente stai usando un fragmentId di destinazione sbagliato (ad esempio, R.id.my_next_fragpotrebbe essere come login o splash screen); anche se si vuole popBack solo uno, è possibile utilizzare: NavHostFragment.findNavController(this).popBackStack().
Pablo Reyes

2
@PabloReyes Questa è la risposta corretta. Basta specificare quale frammento è l'ultimo da rimuovere: Supponiamo di avere questo stack: Fragment_1 Fragment_2 Fragment_3 Ora sei su Fragment_3 e vuoi navigare su Fragment_4 e cancellare tutti gli altri frammenti. Basta specificare nella navigazione xml: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413

5

Ecco come lo ottengo.

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

Perché la tua bandiera inclusivafalse ?
IgorGanapolsky

2
Secondo la documentazione inclusive boolean: true to also pop the given destination from the back stack Quindi, ho impostato inclusivo su false perché non voglio estrarre il frammento corrente dallo stack.
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);
    }

1

Puoi sovrascrivere il backpress dell'attività di base in questo modo:

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

Nota: questo potrebbe non essere correlato alla domanda effettiva.

Questo approccio sarà utile se è necessaria una singola istanza di un frammento da mantenere in Navigation .

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

Ecco, navOptionsè DSL e deve essere importato. Al momento di questa risposta sto usando le seguenti versioni graduali dell'interfaccia utente di navigazione .

    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

Ho lottato per un po 'per evitare che il pulsante Indietro tornasse al mio frammento iniziale, che nel mio caso era un messaggio introduttivo che dovrebbe apparire solo una volta.

La soluzione più semplice era creare un'azione globale che puntasse alla destinazione su cui l'utente doveva rimanere. Devi impostarlo app:popUpTo="..."correttamente: impostalo sulla destinazione che desideri ottenere. Nel mio caso era il mio messaggio introduttivo. Anche impostatoapp:popUpToInclusive="true"


0

Lo posso spiegare con un piccolo esempio. Ho due frammenti di elenco dei dipendenti ed elimino il dipendente. In Elimina dipendente, ho due eventi di clic per annullare ed eliminare. Se faccio clic su Annulla, torna semplicemente alla schermata dell'elenco dei dipendenti e se faccio clic su Elimina, desidero cancellare tutti i precedenti backstack e andare alla schermata dell'elenco dei dipendenti.

Per andare alla schermata precedente:

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


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

Per cancellare tutto il backstack precedente e andare alla nuova schermata:

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

Per ulteriori informazioni: Esempio di navigazione Jetpack


0

Puoi fare semplicemente come:

getFragmentManager().popBackStack();

Se vuoi controllare il conteggio puoi fare come:

getFragmentManager().getBackStackEntryCount()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.