Masalah dengan back-stack Android Fragment

121

Saya punya masalah besar dengan cara kerja fragmen android backstack dan akan sangat berterima kasih atas bantuan yang ditawarkan.

Bayangkan Anda memiliki 3 Fragmen

[1] [2] [3]

Saya ingin pengguna dapat menavigasi [1] > [2] > [3]tetapi dalam perjalanan kembali (menekan tombol kembali) [3] > [1].

Seperti yang saya bayangkan, ini akan dilakukan dengan tidak memanggil addToBackStack(..)saat membuat transaksi yang membawa fragmen [2]ke pemegang fragmen yang ditentukan dalam XML.

Kenyataannya seolah-olah jika saya tidak ingin [2]muncul lagi ketika pengguna menekan tombol kembali [3], saya tidak boleh memanggil addToBackStacktransaksi yang menunjukkan fragmen [3]. Ini tampaknya benar-benar kontra-intuitif (mungkin berasal dari dunia iOS).

Bagaimanapun jika saya melakukannya dengan cara ini, ketika saya pergi dari [1] > [2]dan menekan kembali saya tiba kembali [1]seperti yang diharapkan.

Jika saya pergi [1] > [2] > [3]dan kemudian menekan kembali saya melompat kembali ke [1](seperti yang diharapkan). Sekarang perilaku aneh terjadi ketika saya mencoba dan melompat [2]lagi dari [1]. Pertama-tama [3]ditampilkan secara singkat sebelum [2]terlihat. Jika saya menekan kembali saat ini [3]ditampilkan, dan jika saya menekan kembali sekali lagi aplikasi akan keluar.

Adakah yang bisa membantu saya untuk memahami apa yang terjadi di sini?


Dan berikut ini file layout xml untuk aktivitas utama saya:

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

        />



Perbarui Ini adalah kode yang saya gunakan untuk membangun oleh nav heirarchy

    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]

Terimakasih banyak

Chris Birch
sumber
tetapi fragmen tumpang tindih, saat saya menekan dari Fragmen C ke fragmen A.
Priyanka
Saya memiliki masalah yang sama, bagaimana cara Anda memperbaikinya?
Priyanka
lihat ans saya .. semoga bisa membantu Anda < stackoverflow.com/questions/14971780/… >
MD Khali

Jawaban:

203

Penjelasan: apa yang terjadi disini?

Jika kita ingat bahwa .replace()itu sama dengan .remove().add()yang kita ketahui dari dokumentasi:

Ganti fragmen yang ada yang ditambahkan ke penampung. Ini pada dasarnya sama dengan memanggil remove(Fragment)semua fragmen yang ditambahkan saat ini yang ditambahkan dengan yang sama containerViewIddan kemudian add(int, Fragment, String)dengan argumen yang sama yang diberikan di sini.

maka yang terjadi adalah seperti ini (saya menambahkan angka ke frag untuk membuatnya lebih jelas):

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

(di sini semua hal yang menyesatkan mulai terjadi)

Ingatlah bahwa .addToBackStack()hanya menyimpan transaksi, bukan fragmen itu sendiri! Jadi sekarang kita memiliki frag3tata letak:

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

Solusi yang memungkinkan

Pertimbangkan menerapkan FragmentManager.BackStackChangedListeneruntuk memperhatikan perubahan di back-stack dan menerapkan logika Anda dalam onBackStackChanged()metode:

Arvis
sumber
Penjelasan bagus @arvis. Namun, bagaimana kita dapat mencegah perilaku ini tanpa menggunakan cara hacky seperti yang dilakukan DexterMoon, atau dari Nemanja menggunakan popBackStack yang menampilkan Fragmen saat memutar animasi transisi?
momo
@momo mungkin Anda terapkan FragmentManager.BackStackChangedListeneruntuk mengawasi perubahan pada back-stack. Pantau semua transaksi Anda dengan onBackStackChanged()metode dan bertindak seperlunya: misalnya. melacak hitungan transaksi di BackStack; periksa transaksi tertentu berdasarkan nama ( FragmentTransaction addToBackStack (String name)) dll.
Arvis
Terimakasih telah menjawab. Saya benar-benar mencobanya hari ini, mendaftarkan pendengar dan menghapus fragmen dari onBackstackChange. Sementara transisi popping memainkan fragmen menjadi area kosong putih. Saya kira metode ini diaktifkan saat popping memulai animasi dan bukan saat berakhir ...
momo
4
Hindari menggunakan tumpukan belakang! itu tidak terlalu membantu dengan efisiensi keseluruhan! gunakan ganti biasa () atau bahkan hapus / tambahkan lebih baik setiap kali Anda ingin menavigasi!
stack_ved
@ Arvis dapatkah u tolong bantu saya dengan masalah yang sama .... jumlah tumpukan 0 tetapi masih fragmen saya terlihat?
Erum
33

Baik!!! setelah banyak mencabut rambut akhirnya saya menemukan cara untuk membuat ini bekerja dengan benar.

Tampaknya fragmen [3] tidak terhapus dari tampilan saat bagian belakang ditekan sehingga Anda harus melakukannya secara manual!

Pertama-tama, jangan gunakan replace () melainkan gunakan hapus dan tambahkan secara terpisah. Sepertinya replace () tidak berfungsi dengan baik.

Bagian selanjutnya adalah mengganti metode onKeyDown dan menghapus fragmen saat ini setiap kali tombol kembali ditekan.

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

}

Semoga ini membantu!

Chris Birch
sumber
ini berfungsi, tetapi ini tidak dapat digunakan dalam proyek saya karena fragmen dimuat ulang! ada saran?
TharakaNirmana
Tapi, Jika saya melakukan ini. Saya mendapatkan layar kosong ketika saya kembali dari C ke A untuk Fragmen C.
Nigam Patro
Saya curiga jawaban @Arvis harus menjelaskan masalah Anda
Chris Birch
16

Pertama-tama, terima kasih @Arvis untuk penjelasan yang membuka mata.

Saya lebih suka solusi berbeda daripada jawaban yang diterima di sini untuk masalah ini. Saya tidak suka mengotak-atik perilaku menimpa kembali lebih dari yang benar-benar diperlukan dan ketika saya mencoba menambahkan dan menghapus fragmen sendiri tanpa poping back stack default ketika tombol kembali ditekan saya menemukan diri saya di neraka fragmen :) Jika Anda. tambahkan f2 di atas f1 saat Anda menghapusnya f1 tidak akan memanggil metode callback seperti onResume, onStart, dll. dan itu bisa sangat disayangkan.

Bagaimanapun, inilah cara saya melakukannya:

Saat ini yang dipamerkan hanya fragmen f1.

f1 -> f2

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

tidak ada yang luar biasa di sini. Kemudian di fragmen f2 kode ini membawa Anda ke fragmen f3.

f2 -> f3

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

Saya tidak yakin dengan membaca dokumen apakah ini berfungsi, metode transaksi poping ini dikatakan asinkron, dan mungkin cara yang lebih baik adalah memanggil popBackStackImmediate (). Tapi sejauh yang saya tahu di perangkat saya, ini berfungsi dengan sempurna.

Alternatif yang dimaksud adalah:

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

Di sini sebenarnya akan ada singkat kembali ke f1 beofre pindah ke f3, jadi ada sedikit kesalahan di sana.

Ini sebenarnya yang harus Anda lakukan, tidak perlu menimpa perilaku back-stack ...

Nemanja Kovacevic
sumber
6
tapi kesalahannya, itu masalah
Zyoo
Ini tampaknya kurang "hack-ey" daripada mendengarkan perubahan BackStack. Terima kasih.
ahaisting
13

Saya tahu ini pertanyaan lama tetapi saya mendapat masalah yang sama dan memperbaikinya seperti ini:

Pertama, Tambahkan Fragment1 ke BackStack dengan nama (misalnya "Frag1"):

frag = new Fragment1();

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

Dan kemudian, Setiap kali Anda ingin kembali ke Fragment1 (bahkan setelah menambahkan 10 fragmen di atasnya), panggil popBackStackImmediate dengan nama:

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

Semoga ini akan membantu seseorang :)

Shirane85
sumber
1
Jawaban yang bagus. Saya harus menggunakan getSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); untuk membuatnya berfungsi untuk kasus penggunaan saya
eliasbagley
1
Di mana saya harus meletakkan kode ini ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); Pada MainActivty atau onBackPress
pavel
ini tidak bekerja. :( ini kode saya, Dalam kasus saya Fragmen Tumpang tindih. Ini membuka Fragmen A, tetapi Fragmen A tumpang tindih pada Fragmen 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

Setelah @Arvis membalas saya memutuskan untuk menggali lebih dalam dan saya telah menulis artikel teknologi tentang ini di sini: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- karena-ke-backstack-mimpi buruk-di-android /

Untuk pengembang yang malas di sekitar. Solusi saya terdiri dari selalu menambahkan transaksi ke backstack dan melakukan ekstra FragmentManager.popBackStackImmediate()saat diperlukan (secara otomatis).

Kode ini sangat sedikit baris kode dan, dalam contoh saya, saya ingin melompat dari C ke A tanpa melompat kembali ke "B" jika pengguna tidak masuk lebih dalam di backstack (misal dari C menavigasi ke D).

Karenanya kode yang dilampirkan akan bekerja sebagai berikut A -> B -> C (kembali) -> A & A -> B -> C -> D (kembali) -> C (kembali) -> B (kembali) -> A

dimana

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

dikeluarkan dari "B" ke "C" seperti dalam pertanyaan.

Ok, Ok ini kodenya :)

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
sumber
1
mengalami error pada baris ini fragmentManager.popBackStackImmediate (); error: java.lang.IllegalStateException: FragmentManager sudah menjalankan transaksi di com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)
Priyanka
1

Jika Anda Berjuang dengan addToBackStack () & popBackStack () maka cukup gunakan

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

Dalam Aktivitas Anda Di OnBackPressed () temukan fargment dengan tag dan kemudian lakukan barang Anda

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

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

Untuk Informasi lebih lanjut https://github.com/DattaHujare/NavigationDrawer Saya tidak pernah menggunakan addToBackStack () untuk menangani fragmen.

Dattu Hujare
sumber
0

Saya pikir, ketika saya membaca cerita Anda, [3] juga ada di belakang. Ini menjelaskan mengapa Anda melihatnya berkedip.

Solusinya adalah tidak pernah mengatur [3] pada stack.

jdekeij
sumber
Hai jdekei Terima kasih atas masukan Anda. Masalahnya adalah saya tidak bisa melihat di mana saya menambahkan [3] ke backstack. Saya telah menambahkan potongan kode lain yang mendemonstrasikan (secara terprogram) dengan tepat navigasi yang saya lakukan dengan menggunakan tombol.
Chris Birch
Ini membantu saya juga, tetapi saya hanya menggunakan removeCurFragment dari kode Anda dan sedikit pemeriksa fragmen lain. Metode ganti berfungsi untuk saya baik-baik saja, mungkin, tidak apa- s old method issue but now itapa. Terima kasih
Viktor V.
0

Saya memiliki masalah serupa di mana saya memiliki 3 fragmen berturut-turut di Activity[M1.F0] -> [M1.F1] -> [M1.F2] yang sama diikuti dengan panggilan ke Activity[M2] baru. Jika pengguna menekan tombol di [M2], saya ingin kembali ke [M1, F1] daripada [M1, F2] yang sudah dilakukan oleh perilaku back press.

Untuk melakukannya, saya menghapus [M1, F2], panggil acara di [M1, F1], lakukan transaksi, lalu tambahkan [M1, F2] kembali dengan memanggilnya dengan sembunyikan. Ini menghapus pers belakang ekstra yang seharusnya tertinggal.

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

Hai Setelah melakukan kode ini: Saya tidak dapat melihat nilai Fragment2 saat menekan Tombol Kembali. Kode Saya:

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

executePendingTransactions(), commitNow()tidak bekerja (

Bekerja di 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
sumber