Probleme mit dem Android Fragment Back Stack

121

Ich habe ein massives Problem mit der Funktionsweise des Android-Fragment-Backstacks und wäre für jede angebotene Hilfe sehr dankbar.

Stellen Sie sich vor, Sie haben 3 Fragmente

[1] [2] [3]

Ich möchte, dass der Benutzer [1] > [2] > [3]auf dem Rückweg navigieren kann (Drücken der Zurück-Taste) [3] > [1].

Wie ich mir vorgestellt hätte, würde dies dadurch erreicht, dass addToBackStack(..)beim Erstellen der Transaktion, die das Fragment [2]in den in XML definierten Fragmenthalter bringt, nicht aufgerufen wird.

Die Realität sieht so aus, als ob ich die Transaktion, die ein Fragment anzeigt , nicht aufrufen darf , wenn ich nicht [2]wieder erscheinen möchte , wenn der Benutzer die Zurück-Taste drückt . Dies scheint völlig kontraintuitiv zu sein (möglicherweise aus der iOS-Welt).[3]addToBackStack[3]

Wie auch immer, wenn ich es so mache, wenn ich von [1] > [2]zurück gehe und zurückdrücke, komme ich [1]wie erwartet zurück.

Wenn ich gehe [1] > [2] > [3]und dann zurück drücke, springe ich zurück zu [1](wie erwartet). Jetzt passiert das seltsame Verhalten, wenn ich versuche, noch [2]einmal von zu springen [1]. Zunächst [3]wird kurz angezeigt, bevor es [2]sichtbar wird. Wenn ich an dieser Stelle zurückdrücke, [3]wird angezeigt, und wenn ich erneut zurückdrücke, wird die App beendet.

Kann mir jemand helfen zu verstehen, was hier los ist?


Und hier ist die Layout-XML-Datei für meine Hauptaktivität:

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

        />



Update Dies ist der Code, den ich zum Erstellen durch nav heirarchy verwende

    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]

Danke vielmals

Chris Birch
quelle
aber Fragment überlappt, wenn ich von Fragment C zu Fragment A
zurückdrücke
Ich habe das gleiche Problem, wie beheben Sie das?
Priyanka
Verweisen Sie auf meine Ans .. es kann Ihnen helfen < stackoverflow.com/questions/14971780/… >
MD Khali

Antworten:

203

Erklärung: Was ist hier los?

Wenn wir bedenken, dass dies dem .replace()entspricht .remove().add(), was wir aus der Dokumentation wissen:

Ersetzen Sie ein vorhandenes Fragment, das einem Container hinzugefügt wurde. Dies ist im Wesentlichen dasselbe wie das Aufrufen remove(Fragment)aller aktuell hinzugefügten Fragmente, die mit demselben containerViewIdund dann add(int, Fragment, String)mit denselben Argumenten hinzugefügt wurden, die hier angegeben sind.

dann ist das, was passiert, so (ich füge dem Frag Zahlen hinzu, um es klarer zu machen):

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

(hier beginnt alles irreführende Zeug zu passieren)

Denken Sie daran, dass .addToBackStack()nur die Transaktion gespeichert wird, nicht das Fragment als solches! Nun haben wir also frag3das Layout:

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

Mögliche Lösung

Erwägen Sie die Implementierung FragmentManager.BackStackChangedListener, um nach Änderungen im Backstack zu suchen und Ihre Logik in der onBackStackChanged()Methode anzuwenden :

Arvis
quelle
Gute Erklärung @arvis. Wie können wir dieses Verhalten jedoch verhindern, ohne auf hackige Methoden wie die von DexterMoon oder die von Nemanja mit popBackStack zurückzugreifen, die das Fragment während der Wiedergabe der Übergangsanimation anzeigen?
Momo
@momo, das Sie möglicherweise implementieren FragmentManager.BackStackChangedListener, um nach Änderungen am Backstack zu suchen . Überwachen Sie alle Ihre Transaktionen mit onBackStackChanged()Methode und handeln Sie nach Bedarf: zum Beispiel. Verfolgen Sie die Anzahl der Transaktionen in BackStack. Überprüfen Sie bestimmte Transaktion durch Name ( FragmentTransaction addToBackStack (String name)) usw.
Arvis
Danke für die Antwort. Ich habe das heute früher versucht, den Listener registriert und das Fragment aus onBackstackChange entfernt. Während der Popping-Übergang wird das Fragment zu einem weißen leeren Bereich. Ich denke, die Methode wird ausgelöst, wenn das Poppen die Animation startet und nicht, wenn sie endet ...
Momo
4
Vermeiden Sie Rückstapel! es hilft nicht wirklich bei der Gesamteffizienz! Verwenden Sie plain replace () oder entfernen oder fügen Sie jedes Mal, wenn Sie navigieren möchten, noch besser hinzu!
stack_ved
@Arvis Kannst du mir helfen, dass ich das gleiche Problem bekomme? Stapelanzahl 0, aber mein Fragment ist immer noch sichtbar?
Erum
33

Richtig!!! Nach langem Ziehen der Haare habe ich endlich herausgefunden, wie man das richtig macht.

Es scheint, als würde Fragment [3] nicht aus der Ansicht entfernt, wenn der Rücken gedrückt wird. Sie müssen dies also manuell tun!

Verwenden Sie zunächst nicht replace (), sondern entfernen und fügen Sie separat hinzu. Es scheint, als ob replace () nicht richtig funktioniert.

Der nächste Teil dazu ist das Überschreiben der onKeyDown-Methode und das Entfernen des aktuellen Fragments bei jedem Drücken der Zurück-Taste.

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

}

Hoffe das hilft!

Chris Birch
quelle
Dies funktioniert, kann aber in meinem Projekt nicht verwendet werden, da das Fragment neu geladen wird! irgendwelche Vorschläge?
TharakaNirmana
Aber wenn ich das mache. Ich bekomme einen leeren Bildschirm, wenn ich für Fragment C von C nach A zurückkomme.
Nigam Patro
Ich vermute, @Arvis Antwort sollte etwas Licht auf Ihr Problem werfen
Chris Birch
16

Zunächst einmal danke @Arvis für eine Erklärung, die die Augen öffnet.

Ich bevorzuge eine andere Lösung als die hier akzeptierte Antwort für dieses Problem. Ich mag es nicht mehr als unbedingt notwendig, mit dem Überschreiben des Back-Verhaltens herumzuspielen, und als ich versucht habe, Fragmente selbst hinzuzufügen und zu entfernen, ohne dass der Standard-Back-Stack beim Drücken der Back-Taste platzt, habe ich mich in der Fragment-Hölle wiedergefunden :) Wenn Sie. Wenn Sie f2 über f1 hinzufügen, wenn Sie es entfernen, ruft f1 keine der Rückrufmethoden wie onResume, onStart usw. auf, und das kann sehr unglücklich sein.

Jedenfalls mache ich das so:

Derzeit ist nur das Fragment f1 zu sehen.

f1 -> f2

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

nichts Außergewöhnliches hier. Als in Fragment f2 führt Sie dieser Code zu Fragment f3.

f2 -> f3

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

Ich bin mir nicht sicher, ob diese Poping-Transaktionsmethode beim Lesen von Dokumenten asynchron sein soll. Ein besserer Weg wäre vielleicht, popBackStackImmediate () aufzurufen. Aber soweit ich auf meinen Geräten feststellen kann, funktioniert es einwandfrei.

Die besagte Alternative wäre:

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

Hier wird es tatsächlich einen kurzen Rückblick auf f1 geben, bevor auf f3 übergegangen wird, also ein kleiner Fehler dort.

Dies ist eigentlich alles, was Sie tun müssen, ohne das Verhalten des Backstacks überschreiben zu müssen ...

Nemanja Kovacevic
quelle
6
aber die Panne, das ist ein Problem
Zyoo
Dies scheint weniger "hack-ey" zu sein, als auf BackStack-Änderungen zu warten. Vielen Dank.
ahaisting
13

Ich weiß, dass es eine alte Frage ist, aber ich habe das gleiche Problem und behebe es wie folgt:

Fügen Sie zunächst Fragment1 mit einem Namen (z. B. "Frag1") zu BackStack hinzu:

frag = new Fragment1();

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

Wenn Sie dann zu Fragment1 zurückkehren möchten (auch nachdem Sie 10 Fragmente darüber hinzugefügt haben), rufen Sie einfach popBackStackImmediate mit dem Namen auf:

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

Hoffe es wird jemandem helfen :)

Shirane85
quelle
1
Gute Antwort. Ich musste getSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE) verwenden. damit es für meinen Anwendungsfall
funktioniert
1
Wo muss ich diesen Code setzen ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); Auf MainActivty oder onBackPress
Pavel
es funktioniert nicht. :( Dies ist mein Code. In meinem Fall überlappt sich das Fragment. Es öffnet das Fragment A, aber Frgement A überlappt das Fragment B.
Priyanka
FragmentManager fragmentManager = getActivity (). GetSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE fragmentE = new FragmentE (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Priyanka
5

Nach der Antwort von @Arvis habe ich mich entschlossen, noch tiefer zu graben, und ich habe hier einen technischen Artikel darüber geschrieben: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- wegen-Backstack-Albtraum-in-Android /

Für die faulen Entwickler. Meine Lösung besteht darin, die Transaktionen immer zum Backstack hinzuzufügen und FragmentManager.popBackStackImmediate()bei Bedarf (automatisch) eine zusätzliche Transaktion durchzuführen .

Der Code besteht aus sehr wenigen Codezeilen, und in meinem Beispiel wollte ich von C nach A springen, ohne zu "B" zurückzuspringen, wenn der Benutzer nicht tiefer in den Backstack gegangen ist (z. B. von C nach D).

Daher würde der angehängte Code wie folgt funktionieren: A -> B -> C (zurück) -> A & A -> B -> C -> D (zurück) -> C (zurück) -> B (zurück) -> A.

wo

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

wurden wie in der Frage von "B" nach "C" ausgegeben.

Ok, ok hier ist der Code :)

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();
        }
      }
    }
  });
}
Andrea Baccega
quelle
1
Absturz in dieser Zeile erhalten fragmentManager.popBackStackImmediate (); Fehler: java.lang.IllegalStateException: FragmentManager führt bereits Transaktionen unter com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43) aus
Priyanka
1

Wenn Sie mit addToBackStack () & popBackStack () zu kämpfen haben, verwenden Sie einfach

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

Finden Sie in Ihrer Aktivität in OnBackPressed () die Farcement nach Tag heraus und erledigen Sie dann Ihre Aufgaben

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

Weitere Informationen https://github.com/DattaHujare/NavigationDrawer Ich verwende addToBackStack () niemals zur Behandlung von Fragmenten.

Dattu Hujare
quelle
0

Ich denke, wenn ich Ihre Geschichte lese, ist [3] auch im Hintergrund. Dies erklärt, warum es aufleuchtet.

Die Lösung wäre, niemals [3] auf den Stapel zu setzen.

jdekeij
quelle
Hallo jdekei Danke für deine Eingabe. Das Problem ist, dass ich nicht sehen kann, wo ich [3] zum Backstack hinzufüge. Ich habe einen weiteren Codeabschnitt hinzugefügt, der (programmgesteuert) genau die Navigation demonstriert, die ich mithilfe von Schaltflächen ausgeführt habe.
Chris Birch
Es hilft mir auch, aber ich verwende nur removeCurFragment aus Ihrem Code und ein bisschen eine andere Fragmentprüfung. Ersetzungsmethode funktioniert gut für mich, vielleicht ist das in s old method issue but now itOrdnung. Danke
Viktor V.
0

Ich hatte ein ähnliches Problem, bei dem ich 3 aufeinanderfolgende Fragmente in demselben Activity[M1.F0] -> [M1.F1] -> [M1.F2] hatte, gefolgt von einem Aufruf eines neuen Activity[M2]. Wenn der Benutzer eine Taste in [M2] drückte, wollte ich zu [M1, F1] anstelle von [M1, F2] zurückkehren, was das Verhalten beim Zurückdrücken bereits tat.

Um dies zu erreichen, entferne ich [M1, F2], rufe show auf [M1, F1] auf, schreibe die Transaktion fest und füge dann [M1, F2] zurück, indem ich sie mit hide aufrufe. Dadurch wurde die zusätzliche Rückpresse entfernt, die sonst zurückgeblieben wäre.

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

Hallo, nachdem ich diesen Code ausgeführt habe: Ich kann den Wert von Fragment2 beim Drücken der Zurück-Taste nicht sehen. Mein Code:

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();
    }
Iñaqui
quelle
0

executePendingTransactions(), commitNow()nicht funktioniert (

Arbeitete in Androidx (Jetpack).

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
tim4dev
quelle