When the dialog is displayed, I get "I can not perform this action after onSaveInstanceState"

Some users report that if they use the quick action in the notification bar, they get closer.

I show a quick action in a notification that calls the "TestDialog" class. In the TestDialog class, after clicking the snooze button, I will show 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"); } 

Error *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Line of code where IllegarStateException is raised:

 snoozeDialog.show(fm, "snooze_dialog"); 

The class extends FragmentActivity, and the class SnoozeDialog extends DialogFragment.

Here is a complete error stack trace:

 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) 

I cannot reproduce this error, but I get a lot of error messages.

Can anyone help how I can fix this error?

+107
android dialog fragment
Mar 31 '13 at 11:57
source share
14 answers

This is a common problem . We solved this problem by overriding show () and handling the exception in the extended DialogFragment class

 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); } } } 

Note that using this method will not change the internal fields of DialogFragment.class:

 boolean mDismissed; boolean mShownByMe; 

In some cases, this may lead to unexpected results. Better to use commitAllowingStateLoss () instead of commit ()

+55
Feb 16 '16 at 11:22
source share

This means that after onSaveInstanceState() commit() onSaveInstanceState() fragment ( show() in the case of DialogFragment dialog).

Android will save your fragment state in onSaveInstanceState() . Thus, if the fragment commit() after onSaveInstanceState() state of the fragment is lost.

As a result, if the activity is killed and recreated later, the fragment will not be added to the activity, which is a bad user interface. This means that Android does not allow you to lose all expenses.

A simple solution is to check if the state is saved.

 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"); } } 

Note: onResumeFragments () is called when fragments resume.

+24
Sep 07 '14 at 1:20
source share
 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(); } 

link: link

+15
Oct 20 '16 at 18:25
source share

If the dialogue is not very important (it’s normal not to show it when the application is closed / no longer displayed), use:

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

And open your dialog (fragment) only at startup:

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

EDIT PROBABLY BEST DECISION:

Where onSaveInstanceState is called in the life cycle is unpredictable, I believe the best solution is to check isSavedInstanceStateDone () as follows:

 /** * 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; } 
+9
Jun 19 '14 at 10:23
source share

In a few days I want to share my solution, as I fixed it, to show DialogFragment, you have to override its show() method and call commitAllowingStateLoss() on the Transaction object. Here is an example in Kotlin:

 override fun show(manager: FragmentManager?, tag: String?) { try { val ft = manager?.beginTransaction() ft?.add(this, tag) ft?.commitAllowingStateLoss() } catch (ignored: IllegalStateException) { } } 
+9
Jan 23 '17 at 19:17
source share

I have encountered this problem for many years.
The Internet is flooded with dozens (hundreds? Thousands) of discussions on this subject, and the confusion and misinformation in them seems plentiful.
To make things even worse, and in the spirit of the xkcd comic "14 standards" I add my answer to the ring.
xkcd 14 standards

cancelPendingInputEvents() , commitAllowingStateLoss() , catch (IllegalStateException e) and similar solutions seem awful.

We hope that the following will easily show how to reproduce and fix the problem:

 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); } }; 
+6
Mar 15 '18 at 20:52
source share

try using FragmentTransaction instead of FragmentManager. I think the code below will solve your problem. If not, please let me know.

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

EDIT:

Fragment transaction

Please check this link. I think this will solve your requests.

+5
Dec 26 '13 at 11:42 on
source share

Many views send high-level events, such as click handlers, to the event queue to trigger pending ones. Therefore, the problem is that "onSaveInstanceState" has already been called for the Activity, but the event queue contains a pending click event. Therefore, when this event is dispatched to your handler

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

and your code makes show IllegalStateException exception.

The easiest solution is to clear the event queue in 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(); } } 
+2
Nov 30 '17 at 14:02
source share

Although this is not officially mentioned anywhere, but I ran into this problem a couple of times. In my experience, there is something wrong with the compatibility library supporting fragments on older platforms, which causes this problem. You use the test using the regular fragment manager API. If nothing works, you can use a regular dialog instead of a dialog fragment.

+1
Dec 24 '13 at 19:09
source share
  • Add this class to your project: (should be in the package android.support.v4.app )
 package android.support.v4.app;


 / **
  * Created by Gil on 8/16/2017.
  * /

 public class StatelessDialogFragment extends DialogFragment {
     / **
      * Display the dialog, adding the fragment using an existing transaction and then committing the
      * transaction whilst allowing state loss. 
* * I would recommend you use {@link #show (FragmentTransaction, String)} most of the time but * this is for dialogs you reallly don't care about. (Debug / Tracking / Adverts etc.) * * @param transaction * An existing transaction in which to add the fragment. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Returns the identifier of the committed transaction, as per * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = true; transaction.add (this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Display the dialog, adding the fragment to the given FragmentManager. This is a convenience * for explicitly creating a transaction, adding the fragment to it with the given tag, and * committing it without careing about state. This does not add the transaction to the * back stack. When the fragment is dismissed, a new transaction will be executed to remove it * from the activity.
* * I would recommend you use {@link #show (FragmentManager, String)} most of the time but this is * for dialogs you reallly don't care about. (Debug / Tracking / Adverts etc.) * * * @param manager * The FragmentManager this fragment will be added to. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (this, tag); ft.commitAllowingStateLoss (); } }
  1. Extend StatelessDialogFragment instead of DialogFragment
  2. Use the showAllowingStateLoss method instead of show

  3. Enjoy;)

+1
Aug 16 '17 at 9:38 on
source share

Make the object of your dialog fragment global and call dismissAllowingStateLoss () in the onPause () method

 @Override protected void onPause() { super.onPause(); if (dialogFragment != null) { dialogFragment.dismissAllowingStateLoss(); } } 

Remember to assign a value in the fragment and call show () at the click of a button or somewhere else.

+1
Oct. 16 '18 at 13:44
source share

The following implementation can be used to solve the problem of making safe state changes during the Activity life cycle, in particular, to display dialogs: if the state of an instance has already been saved (for example, due to a configuration change), it will postpone until a renewed one is executed state.

 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); } } 

Then using a class like this:

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

You can safely display dialog boxes without worrying about the state of the application:

 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, eg Message, Icon, etc. ).create(); } } 

and then call TestDialog.show(this) from your XAppCompatActivity .

If you want to create a more general dialog class with parameters, you can save them in the Bundle with arguments in the show() method and get them using getArguments() in onCreateDialog() .

The whole approach may seem a bit complicated, but once you have created two base classes for actions and dialogs, it is pretty easy to use and works great. It can be used for other Fragment operations that can be affected by the same problem.

0
Mar 20 '17 at 17:17
source share

This error occurs because input events (such as keystrokes or onclick events) are delivered after onSaveInstanceState .

The solution is to override onSaveInstanceState in your activity and cancel all pending events.

 @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(); } } } 
0
09 Feb '18 at 2:42
source share

use this code

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

instead

 yourFragment.show(fm, "fragment_tag"); 
0
Aug 24 '19 at 21:35
source share



All Articles