Cómo reanudar Fragment from BackStack si existe

151

Estoy aprendiendo a usar fragmentos. Tengo tres instancias de Fragmenteso que se inicializan en la parte superior de la clase. Estoy agregando el fragmento a una actividad como esta:

Declarando e inicializando:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Reemplazar / Agregar:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Estos fragmentos funcionan correctamente. Cada fragmento se adjunta a la actividad y se guarda en la pila posterior sin ningún problema.

Entonces, cuando inicio A, Cy luego B, la pila se ve así:

| |
|B|
|C|
|A|
___

Y cuando presiono el botón 'atrás', Bse destruye y Cse reanuda.

Pero, cuando lanzo el fragmento Apor segunda vez, en lugar de reanudar desde la pila posterior, se agrega en la parte superior de la pila posterior

| |
|A|
|C|
|A|
___

Pero quiero reanudar Ay destruir todos los fragmentos encima (si los hay). En realidad, me gusta el comportamiento predeterminado de la pila de respaldo.

¿Cómo logro esto?

Esperado: ( Adebe reanudarse y los fragmentos superiores deben destruirse)

| |
| |
| |
|A|
___

Editar: (sugerido por A - C)

Este es mi código de prueba:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

El problema es que cuando inicio Ay luego Bpresiono 'atrás', Bse elimina y Ase reanuda. y presionar 'atrás' por segunda vez debería salir de la aplicación. Pero muestra una ventana en blanco y tengo que presionar hacia atrás una tercera vez para cerrarla.

Además, cuando inicio A, luego B, luego C, luego Botra vez ...

Esperado:

| |
| |
|B|
|A|
___

Real:

| |
|B|
|B|
|A|
___

¿Debo anular onBackPressed()con alguna personalización o me falta algo?

Kaidul
fuente

Respuestas:

279

Al leer la documentación , hay una manera de hacer estallar la pila posterior en función del nombre de la transacción o la identificación proporcionada por commit. El uso del nombre puede ser más fácil, ya que no debería requerir el seguimiento de un número que puede cambiar y refuerza la lógica de "entrada de pila única".

Como solo desea una entrada de pila de respaldo por Fragment, haga que el nombre del estado de respaldo sea el nombre de la clase del Fragmento (vía getClass().getName()). Luego, cuando reemplace a Fragment, use el popBackStackImmediate()método. Si devuelve verdadero, significa que hay una instancia del Fragmento en la pila posterior. De lo contrario, ejecute la lógica de reemplazo de Fragment.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

EDITAR

El problema es: cuando ejecuto A y luego B, luego presiono el botón Atrás, B se quita y A se reanuda. y presionar nuevamente el botón Atrás debería salir de la aplicación. Pero muestra una ventana en blanco y necesita otra presión para cerrarla.

Esto se debe a que FragmentTransactionse está agregando a la pila posterior para garantizar que podamos reventar los fragmentos en la parte superior más adelante. Una solución rápida para esto es anular onBackPressed()y finalizar la Actividad si la pila posterior contiene solo 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Con respecto a las entradas duplicadas de la pila de respaldo, su declaración condicional que reemplaza el fragmento si no se ha reventado es claramente diferente de lo que es mi fragmento de código original. Lo que está haciendo es agregar a la pila posterior independientemente de si la pila posterior se abrió o no.

Algo como esto debería estar más cerca de lo que quieres:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

El condicional se modificó un poco ya que seleccionar el mismo fragmento mientras estaba visible también causó entradas duplicadas.

Implementación:

Le recomiendo no replaceFragment()desarmar el método actualizado como lo hizo en su código. Toda la lógica está contenida en este método y mover partes puede causar problemas.

Esto significa que debe copiar el replaceFragment()método actualizado en su clase y luego cambiar

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

entonces es simplemente

replaceFragment (fragmentName);

EDITAR # 2

Para actualizar el cajón cuando cambie la pila posterior, cree un método que acepte en un Fragmento y compare los nombres de clase. Si algo coincide, cambie el título y la selección. También agregue un OnBackStackChangedListenery haga que llame a su método de actualización si hay un Fragmento válido.

Por ejemplo, en la Actividad onCreate(), agregue

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

Y el otro método:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Ahora, cada vez que cambie la pila posterior, el título y la posición marcada reflejarán lo visible Fragment.

C.A
fuente
Gracias por tu respuesta. :) Está funcionando parcialmente. He editado mi pregunta con progreso. Por favor, eche un vistazo)
Kaidul
2
@RishabhSrivastava tenga en cuenta que un "pop" generalmente destruye el fragmento superior. En cuanto a qué método se llama primero, depende: eche un vistazo al Ciclo de vida del fragmento. Para su caso, usaría un OnBackstackChangedListenerpara que pueda garantizar que está tratando con el Fragmento correcto.
A - C
2
@RishabhSrivastava Por eso también sugerí el OnBackstackChangedListener.
A - C
1
@AlexanderFarber Estás en lo correcto. Cuando respondí esto, no estaba pensando instanceof:)
A - C
2
Su solución suena bien, pero si tiene tres fragmentos y hace clic en ellos A-B-C-By presiona hacia atrás una vez, volverá Ay no volverá C. Esto se debe a que popBackStackImmediateno solo aparece la etiqueta dada, sino también todo lo anterior. ¿Hay algún trabajo por ahí?
Raeglan
10

Creo que este método puede resolver su problema:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

que se publicó originalmente en esta pregunta

erluxman
fuente
Por favor, avíseme si responder de esta manera va en contra de las reglas que solo quería que las personas puedan ver fácilmente en esta publicación. Gracias ..
erluxman
No estoy seguro de si va en contra de las reglas (probablemente no), pero esto definitivamente es útil
Kathir
1
¿Para qué sirve la tercera línea? -> manager.findFragmentByTag (etiqueta); Parece que no está haciendo nada ...
Rik van Velzen
3

Paso 1: Implemente una interfaz con su clase de actividad

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Paso 2: cuando llame al otro fragmento, agregue este método:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
Vinod Joshi
fuente
2

Sé que es bastante tarde para responder esta pregunta, pero resolví este problema por mí mismo y pensé que valía la pena compartirlo con todos.

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

Y en el onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Vivek Pratap Singh
fuente
1
@ X-Black ... BaseFragment es una clase base para cada fragmento utilizado en la aplicación.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});
gushedaoren
fuente
0

La solución más fácil cambiará esta línea

ft.replace(R.id.content_frame, A); a ft.add(R.id.content_frame, A);

Y dentro de su diseño XML, utilice

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable significa que un dispositivo puntero puede hacer clic en él o un dispositivo táctil.

Focusablesignifica que puede obtener el foco de un dispositivo de entrada como un teclado. Los dispositivos de entrada como los teclados no pueden decidir a qué vista enviar sus eventos de entrada en función de las entradas en sí, por lo que los envían a la vista que tiene el foco.

Hossam Hassan
fuente