Saat menampilkan dialog saya mendapatkan "Tidak dapat melakukan tindakan ini setelah onSaveInstanceState"

121

Beberapa pengguna melaporkan, jika mereka menggunakan tindakan cepat di bilah notifikasi, mereka mendapatkan penutupan paksa.

Saya menunjukkan tindakan cepat dalam notifikasi yang memanggil kelas "TestDialog" . Di kelas TestDialog setelah menekan tombol "snooze", saya akan menampilkan SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

Kesalahannya adalah *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Baris kode tempat IllegarStateException diaktifkan adalah:

snoozeDialog.show(fm, "snooze_dialog");

Kelas memperluas "FragmentActivity" dan kelas "SnoozeDialog" memperluas "DialogFragment".

Berikut adalah jejak tumpukan lengkap dari kesalahan tersebut:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Saya tidak dapat mereproduksi kesalahan ini, tetapi saya mendapatkan banyak laporan kesalahan.

Adakah yang bisa membantu bagaimana cara memperbaiki kesalahan ini?

chrisonline.dll
sumber
2
Apakah Anda menemukan solusinya? Saya memiliki masalah yang sama dengan Anda. Saya telah bertanya di sini: stackoverflow.com/questions/15730878/… Silakan periksa pertanyaan saya dan lihat kemungkinan solusi yang tidak berfungsi untuk kasus saya. Mungkin itu akan berhasil untuk Anda.
rootpanthera
Belum ada solusi :-( Dan saran Anda sudah ditambahkan ke kelas saya.
chrisonline
Periksa jawaban yang diterima dari sini. Ini memecahkan masalah saya: stackoverflow.com/questions/14177781/…
bogdan
4
Apakah Aktivitas Anda terlihat saat dialog ini dipicu? Sepertinya ini disebabkan oleh aplikasi Anda yang mencoba menampilkan dialog yang dilampirkan ke Aktivitas yang telah dijeda / dihentikan.
Kai
stackoverflow.com/questions/7575921/… Anda telah mencoba ini Saya yakin.
Orion

Jawaban:

66

Ini adalah masalah umum . Kami memecahkan masalah ini dengan mengganti show () dan menangani pengecualian di kelas diperpanjang DialogFragment

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Perhatikan bahwa menerapkan metode ini tidak akan mengubah bidang internal DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Ini dapat menyebabkan hasil yang tidak terduga dalam beberapa kasus. Lebih baik gunakan commitAllowingStateLoss () daripada commit ()

Rafael
sumber
3
Tetapi mengapa masalah ini terjadi? Bolehkah mengabaikan kesalahan ini? Apa yang terjadi jika Anda melakukannya? Lagipula, ketika mengklik, itu berarti aktivitas tersebut hidup dan baik ... Bagaimanapun, saya telah melaporkan tentang ini di sini karena saya menganggap ini bug: code.google.com/p/android/issues/detail?id= 207269
pengembang android
1
Bisakah Anda memberi bintang dan / atau memberi komentar di sana?
Pengembang android
2
lebih baik memanggil super.show (manager, tag) di dalam klausa coba-tangkap. Bendera yang dimiliki oleh DialogFragment dapat tetap aman dengan cara ini
Shayan_Aryan
20
Pada titik ini Anda bisa memanggil commitAllowingStateLoss () sebagai ganti commit (). Pengecualian tidak akan dimunculkan.
ARLab
1
@ARLabs Saya membayangkan dalam kasus ini yang akan lebih baik untuk penjawab juga karena jika Anda hanya menangkap pengecualian seperti yang ditunjukkan di sini, dialog pasti tidak akan ditampilkan. Lebih baik menampilkan dialog jika Anda bisa dan mungkin akan hilang jika negara bagian harus dipulihkan. Juga pertahankan penggunaan memori aplikasi Anda rendah di latar belakang sehingga tidak mungkin dimusnahkan.
androidguy
27

Itu berarti Anda commit()( show()dalam kasus DialogFragment) setelahnya onSaveInstanceState().

Android akan menyimpan status fragmen Anda di onSaveInstanceState(). Jadi, jika Anda commit()fragmen setelah onSaveInstanceState()status fragmen akan hilang.

Akibatnya, jika Aktivitas dimatikan dan dibuat ulang nanti, fragmen tidak akan menambah aktivitas yang merupakan pengalaman pengguna yang buruk. Itulah mengapa Android tidak mengizinkan kerugian negara dengan cara apa pun.

Solusi mudahnya adalah memeriksa apakah status sudah disimpan.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Catatan: onResumeFragments () akan dipanggil saat fragmen dilanjutkan.

Pongpat
sumber
1
Bagaimana jika saya ingin menampilkan DialogFragment dalam fragmen lain?
Pengembang android
Solusi kami adalah membuat aktivitas dan kelas basis fragmen dan mendelegasikan onResumeFragments ke fragmen (kami membuat onResumeFragments di kelas basis fragmen). Ini bukan solusi yang bagus tetapi berhasil. Jika Anda memiliki solusi yang lebih baik, beri tahu saya :)
Pongpat
Menurut saya, menampilkan dialog di "onStart" seharusnya berfungsi dengan baik, karena fragmen pasti ditampilkan, tetapi saya masih melihat beberapa laporan kerusakan tentangnya. Saya diinstruksikan untuk mencoba meletakkannya di "onResume" sebagai gantinya. Tentang alternatif, saya melihat ini: twigstechtips.blogspot.co.il/2014/01/… , tapi ini cukup aneh.
Pengembang android
Saya pikir alasan twigstechtips.blogspot.co.il/2014/01/… bekerja karena memulai utas baru dan karenanya semua kode siklus hidup yaitu onStart, onResume, dll. Dipanggil sebelum kode runOnUiThread pernah dijalankan. Itu berarti status sudah pulih sebelum runOnUiThread dipanggil.
Pongpat
2
Saya menggunakan panggilan tunggal untuk memposting (dapat dijalankan). Mengenai getFragmentManager, itu tergantung. Jika Anda ingin membagikan dialog itu dengan aktivitas lain, Anda harus menggunakan getFragmentManager, namun, jika dialog itu hanya ada dengan fragmen getChildFragmentManager tampaknya merupakan pilihan yang lebih baik.
Pongpat
16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ref: tautan

huu duy
sumber
11

Setelah beberapa hari saya ingin berbagi solusi saya bagaimana saya sudah tetap itu, untuk menunjukkan DialogFragment Anda harus menimpa show()metode itu dan memanggil commitAllowingStateLoss()pada Transactionobjek. Berikut adalah contoh di Kotlin:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Dennis Zinkovski
sumber
1
Sehingga pengembang tidak perlu mewarisi dari DialogFragmentAnda bisa mengubah ini menjadi fungsi ekstensi Kotlin dengan tanda tangan berikut: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Selain itu, coba-tangkap tidak diperlukan karena Anda memanggil commitAllowingStateLoss()metode dan bukan commit()metode.
Adil Hussain
10

Jika dialog tidak terlalu penting (tidak apa-apa untuk tidak menampilkannya saat aplikasi ditutup / tidak lagi terlihat), gunakan:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

Dan buka dialog Anda (fragmen) hanya saat kami menjalankan:

if (running) {
    yourDialog.show(...);
}

EDIT, SOLUSI YANG LEBIH BAIK MUNGKIN:

Di mana onSaveInstanceState dipanggil dalam siklus hidup tidak dapat diprediksi, saya pikir solusi yang lebih baik adalah memeriksa isSavedInstanceStateDone () seperti ini:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

@Override
protected void onResume() {
    super.onResume();

    savedInstanceStateDone = false;
}

@Override
protected void onStart() {
    super.onStart();

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
jujur
sumber
Ini tampaknya tidak berhasil, karena saya mendapatkan pengecualian ini pada panggilan metode "onStart" (mencoba menampilkan DialogFragment di sana).
Pengembang android
Anda menyelamatkan hari saya. Terima kasih Frank.
Cüneyt
7

Saya telah mengalami masalah ini selama bertahun-tahun.
Internet dipenuhi dengan puluhan (ratusan? Ribuan?) Diskusi tentang hal ini, dan kebingungan serta disinformasi di dalamnya tampaknya banyak sekali.
Untuk memperburuk situasi, dan dalam semangat xkcd "14 standar" komik, saya melempar jawaban saya ke ring.
xkcd 14 standar

The cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), dan solusi sejenis semua tampak mengerikan.

Semoga berikut ini dengan mudah menunjukkan cara mereproduksi dan memperbaiki masalah:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
swooby
sumber
2
Saya suka orang yang down vote tanpa penjelasan. Daripada hanya memberikan suara rendah, mungkin akan lebih baik jika mereka menjelaskan bagaimana solusi saya cacat? Bisakah saya memilih down voter?
swooby
1
Ya, ini masalah SO, saya menulis masalah ini setiap kali dalam saran, tetapi mereka tidak ingin menyelesaikannya.
CoolMind
2
Saya pikir suara negatif mungkin merupakan hasil dari XKCD yang disematkan, jawaban sebenarnya bukan tempat untuk komentar sosial, (tidak peduli seberapa lucu dan / atau benar).
RestingRobot
6

coba gunakan FragmentTransaction sebagai ganti FragmentManager. Saya pikir kode di bawah ini akan menyelesaikan masalah Anda. Jika tidak, tolong beritahu saya.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

EDIT:

Transaksi Fragmen

Silakan periksa tautan ini. Saya pikir itu akan menyelesaikan pertanyaan Anda.

RIJO RV
sumber
4
Penjelasan apa pun tentang mengapa menggunakan FragmentTransaction memperbaiki masalah akan sangat bagus.
Hemanshu
3
Dialog # show (FragmentManager, tag) melakukan hal yang sama. Ini bukanlah solusi.
William
3
Jawaban ini bukanlah solusinya. DialogFragment # show (ft) dan show (fm) melakukan hal yang persis sama.
danijoo
@danijoo Anda benar bahwa keduanya melakukan pekerjaan yang sama. Tetapi di beberapa ponsel, ada beberapa masalah yang mirip dengan ini jika Anda menggunakan fragmentmanager, bukan fragmenttransaction. Jadi dalam kasus saya, ini menyelesaikan masalah saya.
RIJO RV
6

Menggunakan cakupan siklus hidup baru Activity-KTX sesederhana contoh kode berikut:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Metode ini bisa langsung dipanggil setelah onStop () dan akan berhasil menampilkan dialog setelah onResume () dipanggil setelah kembali.

PenguinDan
sumber
3

Banyak tampilan memposting peristiwa tingkat tinggi seperti penangan klik ke antrean acara untuk menjalankan ditangguhkan. Jadi masalahnya adalah bahwa "onSaveInstanceState" telah dipanggil untuk Aktivitas tetapi antrian acara berisi "peristiwa klik" yang ditangguhkan. Karenanya saat acara ini dikirim ke pawang Anda

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

dan kode Anda showmenjalankan IllegalStateException.

Solusi paling sederhana adalah membersihkan antrian acara, di onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}
sim
sumber
Sudahkah Anda benar-benar memastikan bahwa ini menyelesaikan masalah?
mhsmith
Google telah menambahkan ini ke rilis berikutnya dari pustaka androidx, saat ini dalam versi beta ( activitydan fragment).
mhsmith
1
@mhsmith Saya ingat bahwa solusi ini memecahkan masalah dalam kode saya dengan IllegalStateException
sim
2

Jadikan objek fragmen dialog Anda global dan panggil rejectAllowingStateLoss () dalam metode onPause ()

@Override
protected void onPause() {
    super.onPause();

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

Jangan lupa untuk menetapkan nilai dalam fragmen dan memanggil show () pada klik tombol atau di mana pun.

Rohit Rajpal
sumber
1

Meskipun tidak disebutkan secara resmi di mana pun tetapi saya menghadapi masalah ini beberapa kali. Menurut pengalaman saya, ada sesuatu yang salah dalam library kompatibilitas yang mendukung fragmen pada platform lama yang menyebabkan masalah ini. Anda menggunakan uji ini dengan menggunakan API pengelola fragmen normal. Jika tidak ada yang berhasil, Anda dapat menggunakan dialog normal sebagai ganti fragmen dialog.

Dalvinder Singh
sumber
1
  1. Tambahkan kelas ini ke proyek Anda: (harus dalam paket android.support.v4.app )
paket android.support.v4.app;


/ **
 * Dibuat oleh Gil pada 8/16/2017.
 * /

public class StatelessDialogFragment memperluas DialogFragment {
    / **
     * Tampilkan dialog, tambahkan fragmen menggunakan transaksi yang sudah ada dan kemudian lakukan
     * transaksi sambil membiarkan kerugian negara.
* * Saya akan merekomendasikan Anda menggunakan {@link #show (FragmentTransaction, String)} hampir sepanjang waktu, tetapi * ini untuk dialog yang benar-benar tidak Anda pedulikan. (Debug / Pelacakan / Iklan dll.) * * Transaksi @param * Transaksi yang sudah ada untuk menambahkan fragmen. * @ tag param * Tag untuk fragmen ini, sesuai * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Mengembalikan pengidentifikasi dari transaksi yang dilakukan, sesuai * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = benar; transaction.add (ini, tag); mViewDestroyed = salah; mBackStackId = transaction.commitAllowingStateLoss (); kembali mBackStackId; } / ** * Tampilkan dialog, tambahkan fragmen ke FragmentManager yang diberikan. Ini kenyamanan * untuk secara eksplisit membuat transaksi, menambahkan fragmen ke dalamnya dengan tag yang diberikan, dan * Melakukannya tanpa mempedulikan keadaan. Ini tidak menambahkan transaksi ke * tumpukan belakang. Saat fragmen ditutup, transaksi baru akan dijalankan untuk menghapusnya * dari aktivitas.
* * Saya akan menyarankan Anda menggunakan {@link #show (FragmentManager, String)} hampir sepanjang waktu, tetapi ini * untuk dialog yang benar-benar tidak Anda pedulikan. (Debug / Pelacakan / Iklan dll.) * * * Manajer @param * FragmentManager fragmen ini akan ditambahkan. * @ tag param * Tag untuk fragmen ini, sesuai * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (manajer FragmentManager, tag String) { mDismissed = false; mShownByMe = benar; FragmentTransaction ft = manager.beginTransaction (); ft.add (ini, tag); ft.commitAllowingStateLoss (); } }
  1. Perpanjang StatelessDialogFragment sebagai ganti DialogFragment
  2. Gunakan metode showAllowingStateLoss daripada show

  3. Nikmati ;)

Gil SH
sumber
Untuk apa semua bidang boolean ini? Mengapa tidak dideklarasikan sebagai anggota kelas?
tidak ditentukan
1
Bidang boolean adalah anggota DialogFragment yang dilindungi, namanya jelas menunjukkan kegunaannya dan kita perlu memperbaruinya agar tidak mengganggu logika DialogFragment. Perhatikan bahwa di kelas DialogFragment asli, fungsi ini ada tetapi tanpa akses publik
Gil SH
Ough anggota ini tidak dilindungi, mereka internal. Saya mendapatkan kesalahan kompilasi saat saya memasukkan ke StatelessDialogFragmentdalam salah satu paket saya. Terima kasih bung, Saya akan mengujinya dalam produksi segera.
tidak ditentukan
1

gunakan kode ini

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

dari pada

yourFragment.show(fm, "fragment_tag");
Faxriddin Abdullayev
sumber
1

Saya telah menemukan solusi elegan untuk masalah ini dengan menggunakan refleksi. Masalah dari semua solusi di atas adalah bidang mDismissed dan mShownByMe tidak mengubah statusnya .

Cukup ganti metode "tampilkan" dalam fragmen dialog lembar bawah kustom Anda sendiri seperti contoh di bawah (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }
Рома Богдан
sumber
4
"Saya telah menemukan solusi elegan untuk masalah ini dengan menggunakan refleksi." bagaimana ini elegan?
Mark Buikema
anggun, bergaya, cantik, cerdas, bagus, anggun
Рома Богдан
1
ini adalah satu-satunya solusi yang berhasil untuk saya. Saya pikir ini elegan
MBH
0

Implementasi berikut dapat digunakan untuk memecahkan masalah melakukan perubahan status dengan aman selama Activitysiklus hidup, khususnya untuk menampilkan dialog: jika status instans telah disimpan (misalnya karena perubahan konfigurasi), ia akan menundanya hingga status dilanjutkan telah dilakukan.

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Kemudian gunakan kelas seperti ini:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

Anda dapat dengan aman menampilkan dialog tanpa mengkhawatirkan status aplikasi:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

dan kemudian panggil TestDialog.show(this)dari dalam Anda XAppCompatActivity.

Jika Anda ingin membuat kelas dialog yang lebih umum dengan parameter, Anda dapat menyimpannya di a Bundledengan argumen di show()metode dan mengambilnya dengan getArguments()in onCreateDialog().

Keseluruhan pendekatan mungkin tampak sedikit rumit, tetapi setelah Anda membuat dua kelas dasar untuk aktivitas dan dialog, ini cukup mudah digunakan dan bekerja dengan sempurna. Ini dapat digunakan untuk Fragmentoperasi berbasis lain yang dapat dipengaruhi oleh masalah yang sama.

gicci
sumber
0

Kesalahan ini tampaknya terjadi karena peristiwa masukan (seperti peristiwa key down atau onclick) dikirim setelah onSaveInstanceStatedipanggil.

Solusinya adalah menimpa onSaveInstanceStateAktivitas Anda dan membatalkan acara yang menunggu keputusan.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
William
sumber