from fragments example delete backstack addtobackstack android android-fragments back-stack

fragments - Problemas con Android Fragment back stack



get fragments from backstack (8)

Explicación: ¿qué está pasando aquí?

Si tenemos en cuenta que .replace() es igual a .remove().add() que conocemos por la documentación:

Reemplace un fragmento existente que se agregó a un contenedor. Esto es esencialmente lo mismo que llamar a remove(Fragment) para todos los fragmentos agregados actualmente que se agregaron con el mismo containerViewId y luego add(int, Fragment, String) con los mismos argumentos que aquí.

entonces lo que está sucediendo es así (estoy agregando números al fragmento para que quede más claro):

// transaction.replace(R.id.detailFragment, frag1); Transaction.remove(null).add(frag1) // frag1 on view // transaction.replace(R.id.detailFragment, frag2).addToBackStack(null); Transaction.remove(frag1).add(frag2).addToBackStack(null) // frag2 on view // transaction.replace(R.id.detailFragment, frag3); Transaction.remove(frag2).add(frag3) // frag3 on view

(Aquí empiezan a suceder todas las cosas engañosas)

Recuerde que .addToBackStack() está guardando solo transacciones, no el fragmento como sí mismo. Entonces ahora tenemos frag3 en el diseño:

< press back button > // System pops the back stack and find the following saved back entry to be reversed: // [Transaction.remove(frag1).add(frag2)] // so the system makes that transaction backward!!! // tries to remove frag2 (is not there, so it ignores) and re-add(frag1) // make notice that system doesn''t realise that there''s a frag3 and does nothing with it // so it still there attached to view Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING) // transaction.replace(R.id.detailFragment, frag2).addToBackStack(null); Transaction.remove(frag3).add(frag2).addToBackStack(null) //frag2 on view < press back button > // system makes saved transaction backward Transaction.remove(frag2).add(frag3) //frag3 on view < press back button > // no more entries in BackStack < app exits >

Solución posible

Considere la posibilidad de implementar FragmentManager.BackStackChangedListener para ver los cambios en la pila posterior y aplicar su lógica en onBackStackChanged() method:

Tengo un gran problema con la forma en que funciona el backstack del fragmento de Android y estaría muy agradecido por la ayuda que se te ofrece.

Imagina que tienes 3 fragmentos

[1] [2] [3]

Quiero que el usuario pueda navegar [1] > [2] > [3] pero en el camino de regreso (presionando el botón Atrás) [3] > [1] .

Como me hubiera imaginado, esto se lograría al no llamar a addToBackStack(..) al crear la transacción que trae el fragmento [2] dentro del fragmento definido en XML.

La realidad de esto parece que si no quiero [2] aparecer de nuevo cuando el usuario presiona el botón Atrás en [3] , no debo llamar a addToBackStack en la transacción que muestra el fragmento [3] . Esto parece completamente contra-intuitivo (quizás viniendo del mundo iOS).

De todos modos, si lo hago de esta manera, cuando voy de [1] > [2] y presiono volver, vuelvo a [1] como esperaba.

Si voy [1] > [2] > [3] y luego presiono volver, vuelvo a [1] (como se esperaba). Ahora el extraño comportamiento ocurre cuando trato de saltar a [2] nuevamente desde [1] . En primer lugar [3] se muestra brevemente [3] antes de que [2] aparezca a la vista. Si presiono de nuevo en este punto, aparece [3] , y si presiono nuevamente, la aplicación sale.

¿Alguien puede ayudarme a entender qué está pasando aquí?


Y aquí está el archivo xml de diseño para mi actividad principal:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <fragment android:id="@+id/headerFragment" android:layout_width="match_parent" android:layout_height="wrap_content" class="com.fragment_test.FragmentControls" > <!-- Preview: layout=@layout/details --> </fragment> <FrameLayout android:id="@+id/detailFragment" android:layout_width="match_parent" android:layout_height="fill_parent" />



Actualización Este es el código que estoy usando para compilar por heirarchy nav

Fragment frag; FragmentTransaction transaction; //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack frag = new Fragment1(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.commit(); //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack frag = new Fragment2(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.addToBackStack(null); transaction.commit(); //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] frag = new Fragment3(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.commit(); //END OF SETUP CODE------------------------- //NOW: //Press back once and then issue the following code: frag = new Fragment2(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.addToBackStack(null); transaction.commit(); //Now press back again and you end up at fragment [3] not [1]

Muchas gracias


¡¡¡Derecha!!! después de mucho peinado, finalmente resolví cómo hacer que esto funcione correctamente.

Parece que el fragmento [3] no se elimina de la vista cuando se presiona atrás, ¡así que tienes que hacerlo manualmente!

En primer lugar, no use replace (), sino que use eliminar y agregar por separado. Parece que replace () no funciona correctamente.

La siguiente parte de esto está anulando el método onKeyDown y elimina el fragmento actual cada vez que se presiona el botón Atrás.

@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { this.finish(); return false; } else { getSupportFragmentManager().popBackStack(); removeCurrentFragment(); return false; } } return super.onKeyDown(keyCode, event); } public void removeCurrentFragment() { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); Fragment currentFrag = getSupportFragmentManager().findFragmentById(R.id.detailFragment); String fragName = "NONE"; if (currentFrag!=null) fragName = currentFrag.getClass().getSimpleName(); if (currentFrag != null) transaction.remove(currentFrag); transaction.commit(); }

¡Espero que esto ayude!


Antes que nada, gracias @Arvis por una explicación reveladora.

Prefiero una solución diferente a la respuesta aceptada aquí para este problema. No me gusta meterme con la anulación del comportamiento de la espalda más de lo absolutamente necesario y cuando he intentado agregar y eliminar fragmentos por mi cuenta sin retroceder la pila predeterminada cuando se presiona el botón Atrás me encontré en fragmentación infernal :) Si tú. agrega f2 sobre f1 cuando lo eliminas f1 no llamará a ninguno de los métodos de devolución de llamada como onResume, onStart, etc. y eso puede ser muy desafortunado.

De todos modos, así es como lo hago:

Actualmente en exhibición solo está el fragmento f1.

f1 -> f2

Fragment2 f2 = new Fragment2(); this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

Nada fuera de lo común aquí. Que en el fragmento f2 este código te lleva al fragmento f3.

f2 -> f3

Fragment3 f3 = new Fragment3(); getActivity().getSupportFragmentManager().popBackStack(); getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

No estoy seguro leyendo los documentos si esto debería funcionar, se dice que este método de transacción emergente es asincrónico, y quizás una mejor manera sea llamar a popBackStackImmediate (). Pero hasta donde puedo decir en mis dispositivos, funciona perfectamente.

La dicha alternativa sería:

final FragmentActivity activity = getActivity(); activity.getSupportFragmentManager().popBackStackImmediate(); activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Aquí habrá un breve resumen de f1 antes de pasar a f3, por lo que un pequeño problema allí.

Esto es todo lo que tienes que hacer, no hay necesidad de anular el comportamiento de la pila trasera ...


Creo que cuando leí tu historia, [3] también está en el backstack. Esto explica por qué lo ves parpadeando.

La solución sería nunca establecer [3] en la pila.


Después de la respuesta de @Arvis, decidí profundizar aún más y escribí un artículo técnico sobre esto aquí: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

Para los desarrolladores perezosos alrededor. Mi solución consiste en agregar siempre las transacciones a la backstack y realizar un FragmentManager.popBackStackImmediate() adicional cuando sea necesario (automáticamente).

El código es muy pocas líneas de código y, en mi ejemplo, quería saltar de C a A sin volver a saltar a "B" si el usuario no profundizaba en la pila (por ejemplo, C navega a D).

Por lo tanto, el código adjunto funcionaría como sigue A -> B -> C (volver) -> A y A> - B -> C -> D (volver) -> C (volver) -> B (volver) -> A

dónde

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

fueron emitidos de "B" a "C" como en la pregunta.

Ok, Ok aquí está el código :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) { final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1; fragmentManager.beginTransaction() .replace(R.id.content, fragment, tag) .addToBackStack(tag) .commit(); fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { int nowCount = fragmentManager.getBackStackEntryCount(); if (newBackStackLength != nowCount) { // we don''t really care if going back or forward. we already performed the logic here. fragmentManager.removeOnBackStackChangedListener(this); if ( newBackStackLength > nowCount ) { // user pressed back fragmentManager.popBackStackImmediate(); } } } }); }


Sé que es una vieja pregunta, pero tengo el mismo problema y lo soluciono así:

Primero, agregue Fragment1 a BackStack con un nombre (por ejemplo, "Frag1"):

frag = new Fragment1(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.addToBackStack("Frag1"); transaction.commit();

Y luego, cuando quieras volver a Fragment1 (incluso después de agregar 10 fragmentos encima), simplemente llama a popBackStackImmediate con el nombre:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Espero que ayude a alguien :)


Si está luchando con addToBackStack () y popBackStack (), simplemente use

FragmentTransaction ft =getSupportFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, new HomeFragment(), "Home"); ft.commit();`

En tu actividad, en OnBackPressed (), busca faros por etiqueta y luego haz tus cosas

Fragment home = getSupportFragmentManager().findFragmentByTag("Home"); if (home instanceof HomeFragment && home.isVisible()) { // do you stuff }

Para obtener más información https://github.com/DattaHujare/NavigationDrawer Nunca uso addToBackStack () para manejar fragmentos.


Tuve un problema similar en el que tuve 3 fragmentos consecutivos en la misma Activity [M1.F0] -> [M1.F1] -> [M1.F2] seguidos de una llamada a una nueva Activity [M2]. Si el usuario presiona un botón en [M2], quería volver a [M1, F1] en lugar de [M1, F2], que es lo que hacía el comportamiento de retroceso.

Para lograr esto, remueve [M1, F2], llame a show en [M1, F1], confirme la transacción y luego agregue [M1, F2] de nuevo llamándolo con hide. Esto eliminó la contrapresión extra que de otro modo habría quedado atrás.

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2 final FragmentTransaction ftA = fm.beginTransaction(); ftA.remove(M1F2Fragment); ftA.show(M1F1Fragment); ftA.commit(); final FragmentTransaction ftB = fm.beginTransaction(); ftB.hide(M1F2Fragment); ftB.commit();

Hola Después de hacer este código: no puedo ver el valor de Fragment2 al presionar la tecla Atrás. Mi código:

FragmentTransaction ft = fm.beginTransaction(); ft.add(R.id.frame, f1); ft.remove(f1); ft.add(R.id.frame, f2); ft.addToBackStack(null); ft.remove(f2); ft.add(R.id.frame, f3); ft.commit(); @Override public boolean onKeyDown(int keyCode, KeyEvent event){ if(keyCode == KeyEvent.KEYCODE_BACK){ Fragment currentFrag = getFragmentManager().findFragmentById(R.id.frame); FragmentTransaction transaction = getFragmentManager().beginTransaction(); if(currentFrag != null){ String name = currentFrag.getClass().getName(); } if(getFragmentManager().getBackStackEntryCount() == 0){ } else{ getFragmentManager().popBackStack(); removeCurrentFragment(); } } return super.onKeyDown(keyCode, event); } public void removeCurrentFragment() { FragmentTransaction transaction = getFragmentManager().beginTransaction(); Fragment currentFrag = getFragmentManager().findFragmentById(R.id.frame); if(currentFrag != null){ transaction.remove(currentFrag); } transaction.commit(); }