Cómo borrar la pila de navegación después de navegar a otro fragmento en Android

121

Estoy usando el nuevo componente de arquitectura de navegación en Android y estoy atascado en borrar la pila de navegación después de moverme a un nuevo fragmento.

Ejemplo: estoy en el loginFragment y quiero que este fragmento se borre de la pila cuando navegue al fragmento de inicio para que el usuario no vuelva al loginFragment cuando presione el botón Atrás.

Estoy usando un NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) simple para navegar.

Código actual:

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

Intenté usar NavOptions en navigate () , pero el botón de retroceso todavía me envía de regreso 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);
Youssef El Behi
fuente
Se puede utilizar popBackStacko no añadir LoginFragmenta backstack proporcionar nulla addToBackStack(null);y sustituirla por nuevaFragment
Yupi
Edite su publicación y agregue el código que está usando ahora para navegar
Barns
Edité mi pregunta y agregué mi código
Youssef El Behi
Creo que @Yupi ha proporcionado una buena sugerencia. O puede usar el navigate()método como navigate(int resId, Bundle args, NavOptions navOptions)y proporcionar el NavOptionsque mejor se adapte a su senario
Barns
Intenté usar The NavOptions pero el botón de retroceso todavía me envía de regreso al loginFragment
Youssef El Behi

Respuestas:

179

Primero, agregue atributos app:popUpTo='your_nav_graph_id'y app:popUpToInclusive="true"a la etiqueta de acción.

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

En segundo lugar, navegue hasta el destino, utilizando la acción anterior como parámetro.

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

Consulte los documentos para obtener más información.

NOTA : Si navega usando el método navigate(@IdRes int resId), no obtendrá el resultado deseado. Por lo tanto, utilicé el método navigate(@NonNull NavDirections directions).

Subhojit Shaw
fuente
1
¡La mejor respuesta ya que clearTask ahora está obsoleto en las nuevas versiones del componente de navegación de Android!
Michał Ziobro
14
También se puede usar la identificación de la acción asífindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin
1
¡Increíble! Sin embargo, ahora tengo el glifo de respaldo, pero presionarlo no hace nada. ¿Cuál es una forma limpia de eliminar eso también?
Danish Khan
6
Creo que la clave de esta respuesta radica en que el "popUpTo" (que elimina todos los fragmentos entre su fragmento actual y la primera aparición de la identificación que pasa a este argumento) se establece en la identificación del gráfico de navegación . Efectivamente despejar completamente el backstack. Solo agregué este comentario porque no lo he visto explicado exactamente así, y era lo que estaba buscando. +1!
Scott O'Toole
7
NOTA : No es que la clase "Direcciones" no se generará si no incluyó safe-args gradle. Para obtener más información, visite aquí
Jaydip Kalkani
28

En mi caso, necesitaba eliminar todo en la pila posterior antes de abrir un nuevo fragmento, así que usé este código

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

la primera línea elimina la pila trasera hasta que alcanza el fragmento especificado en mi caso, es el fragmento de inicio, por lo que elimina toda la pila trasera por completo, y cuando el usuario vuelve a hacer clic en fragment_company, cierra la aplicación.

lamba
fuente
1
Que es fragment_apps??
IgorGanapolsky
1
El fragmento actual. Es como si estuvieras en fragmentOne y quisieras ir a fragmentTwo, también necesitas usar navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni
26

Creo que su pregunta se refiere específicamente a cómo usar la aplicación Pop Behavior / Pop To /: popUpTo (en xml)

En la documentación,
emerge a un destino determinado antes de navegar. Esto saca todos los destinos que no coinciden de la pila de actividades hasta que se encuentra este destino.

Ejemplo (aplicación de búsqueda de empleo simple)
mi gráfico start_screen_nav es así:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

si quiero navegar EmployerMainFragmenty hacer estallar todo incluido, startScreenFragment entonces el código será:

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

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

si quiero navegar EmployerMainFragmenty hacer pop todo excluyendo, startScreenFragment entonces el código será:

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

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

si quiero navegar EmployerMainFragmenty aparecer loginFragmentpero no, startScreenFragment entonces el código será:

        <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"/>
Dongkichan
fuente
¿Cómo hacer esto programáticamente en lugar de XML?
IgorGanapolsky
14

NOTA: La tarea clara está obsoleta, la descripción oficial es

Este método está en desuso. Utilice setPopUpTo (int, boolean) con el id del gráfico de NavController y establezca inclusive en true.

Respuesta antigua

Si no quiero ir a través de todo lo que la pelusa en el código, simplemente puede comprobar Clear Tasken Launch Optionslas propiedades de la acción.

Opciones de lanzamiento

Editar: A partir de Android Studio 3.2 Beta 5, Borrar tarea ya no está visible en la ventana Opciones de lanzamiento, pero aún puede usarlo en el código XML de navegación, en la etiqueta de acción , agregando

app:clearTask="true"
Mel
fuente
En su lugar, debe usar el ID de la raíz del gráfico, ya que el mensaje de desactivación del atributo xml de clearTask dice: "configure popUpTo en la raíz de su gráfico y use popUpToInclusive". Lo hice así y logré el mismo comportamiento deseado con findNavController (). Navigate (@IdRes resId, bundle)
artnest
El uso del enfoque recomendado de configurar popUpTo y popUpToInclusive no parece funcionar para acciones entre destinos que no sean el destino de inicio: issuetracker.google.com/issues/116831650
hsson
De hecho, me encuentro con un problema en el que no funciona con el destino de inicio, lo que es frustrante.
Mel
¿Cómo harías esto si no conoces el fragmento al que deseas estallar? Por ejemplo, tengo un fragmento que crea un nuevo elemento. Después de crear, quiero mostrar la vista de detalles de este artículo. Pero no quiero que el usuario vuelva al fragmento de creación después, sino a la vista anterior. Puede ser una lista de todos los elementos o un elemento diferente en el que se basó el nuevo. Entonces, ¿es clearTask la única opción?
findusl
No, en realidad ya no debería usarse. En su lugar, debería utilizar popUpTo inclusive. Actualizaré la respuesta muy pronto con mis hallazgos sobre el uso de popUpTo.
Mel
5

Finalmente lo descubrí gracias a ¿Cómo deshabilitar ARRIBA en Navegación para algún fragmento con el nuevo Componente de Arquitectura de Navegación?

Tuve que especificar .setClearTask (true) como 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
fuente
2
Me alegra ver que pudo implementar mi sugerencia de usar navigate(int resId, Bundle args, NavOptions navOptions)yNavOptions
Barns
Si. Gracias @Barns
Youssef El Behi
14
sí, "setClearTask" está obsoleto, pero en su lugar puede usar: 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 debería funcionar. Probablemente esté utilizando un fragmentId de destino incorrecto (por ejemplo, R.id.my_next_fragpodría ser como una pantalla de inicio de sesión o de presentación); También si quieres popBack sólo uno, puede utilizar: NavHostFragment.findNavController(this).popBackStack().
Pablo Reyes
2
@PabloReyes Esta es la respuesta correcta. Simplemente especifique qué fragmento es el último en eliminar: Digamos que tiene esta pila: Fragment_1 Fragment_2 Fragment_3 Ahora está en Fragment_3 y desea navegar hasta Fragment_4 y borrar todos los demás fragmentos. Simplemente especifique en el xml de navegación: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413
5

Así es como lo estoy haciendo.

 //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
fuente
¿Por qué es tu bandera inclusivafalse ?
IgorGanapolsky
2
De acuerdo con la documentación inclusive boolean: true to also pop the given destination from the back stack , configuré el inclusivo en falso porque no quiero sacar el fragmento actual de la pila.
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
fuente
1

Puede anular la presión hacia atrás de la actividad base de esta manera:

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

Nota: Es posible que esto no se relacione con la pregunta real.

Este enfoque será útil si se necesita una sola instancia de un fragmento para mantener en 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
                            })

Aquí, navOptionses DSL y necesita ser importado. En el momento de esta respuesta, estoy usando las siguientes versiones de Gradle de la interfaz de usuario de navegación .

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

Luché por un tiempo para evitar que el botón de retroceso volviera a mi fragmento de inicio, que en mi caso era un mensaje de introducción que solo debería aparecer una vez.

La solución fácil fue crear una acción global que apuntara al destino en el que el usuario debería permanecer. Tiene que configurarlo app:popUpTo="..."correctamente: configúrelo en el destino que desea que aparezca. En mi caso fue mi mensaje de introducción. También estableceapp:popUpToInclusive="true"

Tyler
fuente
0

Puedo explicar esto con un pequeño ejemplo. Tengo dos fragmentos de la lista de empleados y elimino empleado. En eliminar empleado, tengo dos eventos de clic cancelar y eliminar. Si hago clic en cancelar, simplemente vuelvo a la pantalla de la lista de empleados y si hago clic en eliminar, quiero borrar todas las pilas anteriores e ir a la pantalla de la lista de empleados.

Para ir a la pantalla anterior:

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


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

Para borrar todo el backstack anterior e ir a una nueva pantalla:

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

Para obtener más referencia: Ejemplo de navegación Jetpack

Dhrumil Shah
fuente
0

Puedes hacerlo tan simple como:

getFragmentManager().popBackStack();

Si desea verificar el recuento, puede hacer lo siguiente:

getFragmentManager().getBackStackEntryCount()
Sr. T
fuente