IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager
A

37

574

I'm getting user reports from my app in the market, delivering the following exception:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)

Apparently it has something to do with a FragmentManager, which I don't use. The stacktrace doesn't show any of my own classes, so I have no idea where this exception occurs and how to prevent it.

For the record: I have a tabhost, and in each tab there is a ActivityGroup switching between Activities.

Ageratum answered 27/9, 2011 at 21:31 Comment(9)
I found this question discussing the same issue, but no solution there either.. #7469582Ageratum
While you are not using FragmentManager, Honeycomb certainly is. Is this happening on real Honeycomb tablets? Or might it be that somebody is running a hacked Honeycomb on a phone or something and it's that hacked edition that is having difficulty?Pisgah
I have no idea. This is the only information I get in the Market Developer Console, the user message contains no useful info either..Ageratum
I am using Flurry, which shows me 11 sessions with Android 3.0.1, and I have 11 reports of this exception. Could be coincidence though. Android 3.1 and 3.2 have 56 and 38 sessions, respectively.Ageratum
The Market error report has a 'Platform' section, sometimes it has the Android version of the device in it.Bask
Ah I see, I guess I missed it because it isn't helpful.. Platforms OTHER 11 rapporten/weekAgeratum
Please check my answer [here][1]. Might be helpful [1]: #7469582Interdiction
Here is my solution: https://mcmap.net/q/74312/-java-lang-illegalstateexception-can-not-perform-this-action-after-onsaveinstancestate Hope someone resolve this problem.Wallasey
The best working answer for me was this: https://mcmap.net/q/74313/-android-fragment-transaction-in-background (not compromising state loss either)Gawlas
O
777

Please check my answer here. Basically I just had to :

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

Don't make the call to super() on the saveInstanceState method. This was messing things up...

This is a known bug in the support package.

If you need to save the instance and add something to your outState Bundle you can use the following:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

In the end the proper solution was (as seen in the comments) to use :

transaction.commitAllowingStateLoss();

when adding or performing the FragmentTransaction that was causing the Exception.

Overcautious answered 21/4, 2012 at 17:43 Comment(24)
You should use commitAllowingStateLoss() instead of commit()Increase
This comment about commitAllowingStateLoss() is an answer in its own right - you should post it as that.Converge
Regarding 'commitAllowingStateLoss' --/> "This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user."Miran
If I look at the v4 source for popBackStackImmediate it immediately fails if the state has been saved. Previously adding the fragment with commitAllowingStateLoss doesn't play any part. My testing shows this to be true. It has no effect on this specific exception. What we need is a popBackStackImmediateAllowingStateLoss method.Devilkin
@Ovidiu Looking at sources: Implementation of Fragment#onSaveInstanceState is a NOP. Can you explain why not calling super method helps ?Resinoid
What should i do with dialog fragment? Here we have only call show() method.Gann
@Synesso, you are actually right. The error gets fired by popBackStackImmediate() and not by committing the transaction. Did you find any solution?Irreformable
@Synesso, maybe there are some conditions we can test for, and avoid calling popBackStackImmediate() on those conditions? any idea how to detect these conditions?Irreformable
@DanieleB yes, I've posted an answer here. But I've actually found an even better solution by using Otto message bus: register the fragment as a subscriber and listen for the async result from the bus. Unregister on pause and re-register on resume. The async also needs a Produce method for the times when it completes and the fragment is paused. When I get time I'll update my answer with this in more detail.Devilkin
@Synesso, catching the exception is actually a good solution! that exception only happens very rarely to me anywayIrreformable
Interestingly I only use commitAllowingStateLoss(), tried both workaround in onSaveInstanteState(), still no luck, the issue still occurs once in a while. Only good solution is catching exception, sad.Centigram
Did anyone file an issue that I could star?Burberry
@Ovidiu Latcu Thanks..transaction.commitAllowingStateLoss(); this line saved hours of time..;)Foetor
I think this is not 100% correct, check this link: androiddesignpatterns.com/2013/08/…Breeks
What about dialog fragments? How to commitAllowingStateLoss() while displaying fragment as a dialog.Gymno
I was able to solve it by showing Fragment through a Handler.Peristyle
you are probably calling the fragmentTransaction::commit() from a background thread (using runOnUiThread or handler ). The actual commit is happening bit late. If user exits app (calling onSavedStateInstance()) fast, then this fts commit actually occurs later; resulting this issue. So the solution at this point probably is to allow state loss commitAllowingStateLoss(). User is exiting anyway.Villainous
If you use commitAllowingStateLoss, you probably will encounter the ArrayIndexOutOfBoundsException while the fragmentManager trying to restore its state(not always). Even if I called commit in the onCreate() it still threw the IllegalStateException. I still can't figure out why. I think it's an Android bug.Minnaminnaminnie
Wow i cant believe its 2017 and the suggestion posted by @Increase is still working, android is drop dead buggyFry
@Fry the issue is with doing fragment transactions after callbacks from background jobs when the app is not on the foreground. best way is to avoid doing it because its a bad practice.Increase
I use commitAllowingStateLoss for an app on play store and i'm still getting hundreds of the same exception at this line: fragmentTransaction.replace(layoutId, newFragment).commitAllowingStateLoss();Hereunder
What is transaction here?Translocation
I get this error when I don't call super: Overriding method should call super.onSaveInstanceState. Some methods, such as View#onDetachedFromWindow, require that you also call the super implementation as part of your method.Albuminuria
I didn't use onSaveInstanceState() method. Why it crashed sametimesDarryldarryn
D
150

There are many related problems with a similar error message. Check the second line of this particular stack trace. This exception is specifically related to the call to FragmentManagerImpl.popBackStackImmediate.

This method call, like popBackStack, will always fail with IllegalStateException if the session state has already been saved. Check the source. There is nothing you can do to stop this exception being thrown.

  • Removing the call to super.onSaveInstanceState will not help.
  • Creating the Fragment with commitAllowingStateLoss will not help.

Here's how I observed the problem:

  • There's a form with a submit button.
  • When the button is clicked a dialog is created and an async process starts.
  • The user clicks the home key before the process is finished - onSaveInstanceState is called.
  • The process completes, a callback is made and popBackStackImmediate is attempted.
  • IllegalStateException is thrown.

Here's what I did to solve it:

As it is not possible to avoid the IllegalStateException in the callback, catch & ignore it.

try {
    activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
    // There's no way to avoid getting this if saveInstanceState has already been called.
}

This is enough to stop the app from crashing. But now the user will restore the app and see that the button they thought they'd pressed hasn't been pressed at all (they think). The form fragment is still showing!

To fix this, when the dialog is created, make some state to indicate the process has started.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;

And save this state in the bundle.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}

Don't forget to load it back again in onViewCreated

Then, when resuming, rollback the fragments if submit was previously attempted. This prevents the user from coming back to what seems like an un-submitted form.

@Override
public void onResume() {
    super.onResume();
    if (submitPressed) {
        // no need to try-catch this, because we are not in a callback
        activity.getSupportFragmentManager().popBackStackImmediate(name);
        submitPressed = false;
    }
}
Devilkin answered 9/1, 2015 at 4:55 Comment(6)
Interesting reading about that here: androiddesignpatterns.com/2013/08/…Hara
If you use DialogFragment, I've made an alternative to it here: github.com/AndroidDeveloperLB/DialogShardLu
What if the popBackStackImmediate was called by Android itself?Minnaminnaminnie
Absolutely great. This one should be the accepted answer. Thank you very much! Maybe I would add submitPressed = false; after popBackStackInmediate.Coyne
I didn't use public void onSaveInstanceState(Bundle outState) method. Do I need to set empty method for public void onSaveInstanceState(Bundle outState) ?Darryldarryn
@FakhriddinAbdullaev you absolutely need onSaveInstanceState(), because the user may rotate the screen while the background task is responding, thus you will get exception again.Divergent
G
64

Check if the activity isFinishing() before showing the fragment and pay attention to commitAllowingStateLoss().

Example:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Gravante answered 4/3, 2015 at 1:24 Comment(2)
!isFinishing() && !isDestroyed() doesn't work for me.Lantz
!isFinishing() && !isDestroyed() worked for me, but it requires API 17. But it simply doesn't show a DialogFragment. See #15729638 for other good solutions, https://mcmap.net/q/74314/-on-showing-dialog-i-get-quot-can-not-perform-this-action-after-onsaveinstancestate-quot helped me.Geniagenial
O
41

It's October 2017, and Google makes Android Support Library with the new things call Lifecycle component. It provides some new idea for this 'Can not perform this action after onSaveInstanceState' problem.

In short:

  • Use lifecycle component to determine if it's correct time for popping up your fragment.

Longer version with explain:

  • why this problem come out?

    It's because you are trying to use FragmentManager from your activity(which is going to hold your fragment I suppose?) to commit a transaction for you fragment. Usually this would look like you are trying to do some transaction for an up coming fragment, meanwhile the host activity already call savedInstanceState method(user may happen to touch the home button so the activity calls onStop(), in my case it's the reason)

    Usually this problem shouldn't happen -- we always try to load fragment into activity at the very beginning, like the onCreate() method is a perfect place for this. But sometimes this do happen, especially when you can't decide what fragment you will load to that activity, or you are trying to load fragment from an AsyncTask block(or anything will take a little time). The time, before the fragment transaction really happens, but after the activity's onCreate() method, user can do anything. If user press the home button, which triggers the activity's onSavedInstanceState() method, there would be a can not perform this action crash.

    If anyone want to see deeper in this issue, I suggest them to take a look at this blog post. It looks deep inside the source code layer and explain a lot about it. Also, it gives the reason that you shouldn't use the commitAllowingStateLoss() method to workaround this crash(trust me it offers nothing good for your code)

  • How to fix this?

    • Should I use commitAllowingStateLoss() method to load fragment? Nope you shouldn't;

    • Should I override onSaveInstanceState method, ignore super method inside it? Nope you shouldn't;

    • Should I use the magical isFinishing inside activity, to check if the host activity is at the right moment for fragment transaction? Yeah this looks like the right way to do.

  • Take a look at what Lifecycle component can do.

    Basically, Google makes some implementation inside the AppCompatActivity class(and several other base class you should use in your project), which makes it a easier to determine current lifecycle state. Take a look back to our problem: why would this problem happen? It's because we do something at the wrong timing. So we try not to do it, and this problem will be gone.

    I code a little for my own project, here is what I do using LifeCycle. I code in Kotlin.

val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.

fun dispatchFragment(frag: Fragment) {
    hostActivity?.let {
       if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
           showFragment(frag)
       }
    }
}

private fun showFragment(frag: Fragment) {
    hostActivity?.let {
        Transaction.begin(it, R.id.frag_container)
                .show(frag)
                .commit()
    }

As I show above. I will check the lifecycle state of the host activity. With Lifecycle component within support library, this could be more specific. The code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED) means, if current state is at least onResume, not later than it? Which makes sure my method won't be execute during some other life state(like onStop).

  • Is it all done?

    Of course not. The code I have shown tells some new way to prevent application from crashing. But if it do go to the state of onStop, that line of code wont do things and thus show nothing on your screen. When users come back to the application, they will see an empty screen, that's the empty host activity showing no fragments at all. It's bad experience(yeah a little bit better than a crash).

    So here I wish there could be something nicer: app won't crash if it comes to life state later than onResume, the transaction method is life state aware; besides, the activity will try continue to finished that fragment transaction action, after the user come back to our app.

    I add something more to this method:

class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
    private val hostActivity: FragmentActivity? = _host
    private val lifeCycle: Lifecycle? = _host.lifecycle
    private val profilePendingList = mutableListOf<BaseFragment>()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun resume() {
        if (profilePendingList.isNotEmpty()) {
            showFragment(profilePendingList.last())
        }
    }

    fun dispatcherFragment(frag: BaseFragment) {
        if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
            showFragment(frag)
        } else {
            profilePendingList.clear()
            profilePendingList.add(frag)
        }
    }

    private fun showFragment(frag: BaseFragment) {
        hostActivity?.let {
            Transaction.begin(it, R.id.frag_container)
                    .show(frag)
                    .commit()
        }
    }
}

I maintain a list inside this dispatcher class, to store those fragment don't have chance to finish the transaction action. And when user come back from home screen and found there is still fragment waiting to be launched, it will go to the resume() method under the @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) annotation. Now I think it should be working like I expected.

Outplay answered 4/10, 2017 at 15:21 Comment(3)
It would be nice to use Java instead of KotlinCallant
Why does your implementation of the FragmentDispatcher use a list to store pending fragments if there will be only one fragment restored?Vivie
I prefer this answer. Thanks! If working with FragmentTransactions, we should wait at least to be in the START state, to prevent crashes. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/…Husein
P
22

Here is a different solution to this problem.

Using a private member variable you are able to set the returned data as an intent that can then be processed after super.onResume();

Like so:

private Intent mOnActivityResultIntent = null; 

@Override
protected void onResume() {
    super.onResume();
    if(mOnActivityResultIntent != null){
        ... do things ...
        mOnActivityResultIntent = null;
    }
 }

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    if(data != null){
        mOnActivityResultIntent = data;
    }
}
Peel answered 16/10, 2012 at 5:44 Comment(3)
Depending on what action you were doing that wasn't allowed, this might need to move to an even later moment than onResume(). For insatcne, if FragmentTransaction.commit() is the problem, this needs to go into onPostResume() instead.Spadework
This is THE answer to this question for me. Since I needed to forward a received NFC tag to the previous activity, this is what did it for me.Philharmonic
For me it was happening because I was not calling super.onActivityResult().Flouncing
O
22

Short And working Solution :

Follow Simple Steps

Steps

Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Step 2 : Use fragmentTransaction.commitAllowingStateLoss( );

instead of fragmentTransaction.commit( ); while fragment operations.

Opinion answered 8/4, 2014 at 13:24 Comment(1)
Answer is not copied or refereed form else where .Is was posted to help to people by my working solution which is got by several trial and errorOpinion
E
14

BEWARE, using transaction.commitAllowingStateLoss() could result in a bad experience for the user. For more information on why this exception is thrown, see this post.

Epps answered 13/11, 2015 at 15:29 Comment(1)
This does not provide answer to the question, you must provide a valid answer for the questionPapen
J
10

I found a dirty solution for this kind of problem. If you still want to keep your ActivityGroups for whatever reason (I had time limitation reasons), you just implement

public void onBackPressed() {}

in your Activity and do some back code in there. even if there is no such Method on older Devices, this Method gets called by newer ones.

Jarrell answered 21/2, 2012 at 13:10 Comment(0)
C
6

Do not use commitAllowingStateLoss(), it should only be used for cases where it is okay for the UI state to change unexpectedly on the user.

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()

If the transaction happens in ChildFragmentManager of parentFragment, use parentFragment.isResume() outside to check instead.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
Cnemis answered 19/12, 2016 at 23:38 Comment(0)
B
5

I had a similar problem, the scenario was like this:

  • My Activity is adding/replacing list fragments.
  • Each list fragment has a reference to the activity, to notify the activity when a list item is clicked (observer pattern).
  • Each list fragment calls setRetainInstance(true); in its onCreate method.

The onCreate method of the activity was like this:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

The exception was thrown because the when configuration changes (device rotated), the activity is created, the main fragment is retrieved from the history of the fragment manager and at the same time the fragment already has an OLD reference to the destroyed activity

changing the implementation to this solved the problem:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }
        mMainFragment.setOnSelectionChangedListener(this);

you need to set your listeners each time the activity is created to avoid the situation where the fragments have references to old destroyed instances of the activity.

Balneal answered 18/7, 2015 at 12:28 Comment(0)
M
5

If you inherit from FragmentActivity, you must call the superclass in onActivityResult():

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

If you don't do this and try to show a fragment dialog box in that method, you may get OP's IllegalStateException. (To be honest, I don't quite understand why the super call fixes the problem. onActivityResult() is called before onResume(), so it should still not be allowed to show a fragment dialog box.)

Mcfall answered 30/11, 2018 at 22:23 Comment(1)
Would love to know why this fixes the problem.Wyatan
G
5

Fragment transactions should not be executed after Activity.onStop() ! Check that you do not have any callbacks that could execute transaction after onStop(). It is better to fix the reason instead of trying to walk around the problem with approaches like .commitAllowingStateLoss()

Ginnifer answered 21/6, 2019 at 8:48 Comment(0)
L
4

Possibly the smoothest and the simplest solution I found in my case was to avoid popping the offending fragment off the stack in response to activity result. So changing this call in my onActivityResult():

popMyFragmentAndMoveOn();

to this:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

helped in my case.

Languishment answered 2/6, 2017 at 16:52 Comment(0)
B
3

I was getting this exception when i was pressing back button to cancel intent chooser on my map fragment activity. I resolved this by replacing the code of onResume(where i was initializing the fragment) to onstart() and the app is working fine.Hope it helps.

Baguette answered 14/7, 2014 at 11:35 Comment(0)
E
3

Courtesy: Solution for IllegalStateException

This issue had annoyed me for a lot of time but fortunately I came with a concrete solution for it. A detailed explanation of it is here.

Using commitAllowStateloss() might prevent this exception but would lead to UI irregularities.So far we have understood that IllegalStateException is encountered when we try to commit a fragment after the Activity state is lost- so we should just delay the transaction until the state is restored.It can be simply done like this

Declare two private boolean variables

 public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

Now in onPostResume() and onPause we set and unset our boolean variable isTransactionSafe. Idea is to mark trasnsaction safe only when the activity is in foreground so there is no chance of stateloss.

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

-What we have done so far will save from IllegalStateException but our transactions will be lost if they are done after the activity moves to background, kind of like commitAllowStateloss(). To help with that we have isTransactionPending boolean variable

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}
Easterly answered 30/6, 2018 at 16:4 Comment(0)
B
3

as you can see in your crash report, the last line that is throwing the exception is

checkStateLoss(FragmentManager.java:1109)

if you look at the implementation of checkStateLoss

private void checkStateLoss() {
    if (isStateSaved()) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
}

so a simple solution for me is to find what ever method of the Fragment Manager you are calling in your app that eventually leads to this method being called and simply check if isStateSaved() is false before calling that method. For me it was the show() method. I did like this

if (!isStateSaved()) {
  myDialog.show(fragmentManager, Tag)
}
Burier answered 19/9, 2022 at 10:10 Comment(0)
J
2

I think using transaction.commitAllowingStateLoss(); is not best solution. This exception will be thrown when activity's configuration changed and fragment onSavedInstanceState() is called and thereafter your async callback method tries to commit fragment.

Simple solution could be check whether activity is changing configuration or not

e.g. check isChangingConfigurations()

i.e.

if(!isChangingConfigurations()) { //commit transaction. }

Checkout this link as well

Joellenjoelly answered 13/8, 2015 at 7:40 Comment(5)
Somehow I got this exception when the user clicks on something (the click is the trigger to do the transaction-commit ). How could this be? Would your solution here here?Lu
@androiddeveloper what else are you doing on user click. somehow fragment is saving its state before you commit the transactionJoellenjoelly
The exception was thrown on the exact line of transaction commit. Also, I had a weird typo: instead of "here here" I meant "work here" .Lu
@androiddeveloper you are right! but before committing transaction are you spawning any background thread or something?Joellenjoelly
I don't think so (sorry I'm out of the office), but why would it matter? It's all UI stuff here... If I ever make something on a background thread, I would have exceptions there, plus I don't put UI related stuff on background threads, as it's too risky.Lu
C
2

The exception is threw here (In FragmentActivity):

@Override
public void onBackPressed() {
    if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
        super.onBackPressed();
    }
}

In FragmentManager.popBackStatckImmediate()FragmentManager.checkStateLoss() is called firstly. That's the cause of IllegalStateException. See the implementation below:

private void checkStateLoss() {
    if (mStateSaved) { // Boom!
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }
}

I solve this problem simply by using a flag to mark Activity's current status. Here's my solution:

public class MainActivity extends AppCompatActivity {
    /**
     * A flag that marks whether current Activity has saved its instance state
     */
    private boolean mHasSaveInstanceState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        mHasSaveInstanceState = false;
    }

    @Override
    public void onBackPressed() {
        if (!mHasSaveInstanceState) {
            // avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
            super.onBackPressed();
        }
    }
}
Crosscut answered 7/8, 2017 at 6:19 Comment(0)
I
2

Whenever you are trying to load a fragment in your activity make sure that activity is in resume and not going to pause state.In pause state you may end up losing commit operation that is done.

You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment

or

Create a boolean and check if activity is not going to onpause

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

then while loading fragment check

if(mIsResumed){
//load the your fragment
}
Inrush answered 13/9, 2017 at 7:7 Comment(0)
D
2

If you are doing some FragmentTransaction in onActivityResult what you can do you can set some boolean value inside onActivityResult then in onResume you can do your FragmentTransaction on the basis of the boolean value. Please refer the code below.

@Override
protected void onResume() {
    super.onResume;
    if(isSwitchFragment){
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
        isSwitchFragment=true;
    }
}
Devanagari answered 11/4, 2018 at 9:59 Comment(2)
Please do not post code as an image, use code formatting instead.Radiator
Yes it helped me.., This the exact and proper solution for marshmallow device i got this exception and solved with this simple trick..!! Hence up voted.Diu
Y
2

In regards to @Anthonyeef great answer, here is a sample code in Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

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

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}
Yoo answered 17/7, 2018 at 12:10 Comment(0)
I
2

If you have crash with popBackStack() or popBackStackImmediate() method please try fixt with:

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

This is worked for me as well.

Ingold answered 12/12, 2018 at 9:18 Comment(1)
Note that it requires API 26 and aboveGermangermana
F
2

In my case I got this error in an override method called onActivityResult. After digging I just figure out maybe I needed to call 'super' before.
I added it and it just worked

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) {
        mostrarFragment(FiltroFragment.newInstance())
    }

}

Maybe you just need to add a 'super' on any override you are doing before your code.

Frydman answered 24/1, 2019 at 20:48 Comment(0)
G
2

Kotlin extension

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) {
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let {
        beginTransaction().remove(it).commitNow()
    }
    // Add a fragment.
    this?.beginTransaction()?.run {
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://mcmap.net/q/67420/-is-this-the-right-way-to-clean-up-fragment-back-stack-when-leaving-a-deeply-nested-stack
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

Usage:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
Geniagenial answered 23/7, 2019 at 10:15 Comment(2)
You can remove the () -> before FragmentLorrin
@Claire, thanks! Do you mean changing to fragment: Fragment? Yes, I tried this variant, but in this case a fragment would be created in all cases (even when fragmentManager == null, but I didn't encounter this situation). I updated the answer and changed null to tag in addToBackStack().Geniagenial
T
2

change getFragmentManager() to getChildFragmentManager(). Don't use parent FragmentManager, try to use self.

Thebaine answered 3/12, 2021 at 2:4 Comment(0)
C
1

Starting from support library version 24.0.0 you can call FragmentTransaction.commitNow() method which commits this transaction synchronously instead of calling commit() followed by executePendingTransactions(). As documentation says this approach even better:

Calling commitNow is preferable to calling commit() followed by executePendingTransactions() as the latter will have the side effect of attempting to commit all currently pending transactions whether that is the desired behavior or not.

Colis answered 29/8, 2016 at 20:22 Comment(0)
B
1

I know there is an accepted answer by @Ovidiu Latcu but after some while, error still persist.

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics still sending me this weird error message.

However error now occurring only on version 7+ (Nougat) My fix was to use commitAllowingStateLoss() instead of commit() at the fragmentTransaction.

This post is helpful for commitAllowingStateLoss() and never had a fragment issue ever again.

To sum it up, the accepted answer here might work on pre Nougat android versions.

This might save someone a few hours of searching. happy codings. <3 cheers

Bailar answered 18/5, 2018 at 5:6 Comment(0)
I
1

To bypass this issue, we can use The Navigation Architecture Component , which was introduced in Google I/O 2018. The Navigation Architecture Component simplifies the implementation of navigation in an Android app.

Ileac answered 22/6, 2018 at 9:25 Comment(1)
It's not good enough, and bugy (poor deeplink handling, can't save state show/hide fragments and some important issue that is still open)Tele
L
1

This error IllegalStateException: Can not perform this action after onSaveInstanceState occurs when you trying to create or replace a fragment while the parent activity or parent fragment is in a Stop State and UI doesn't exist.

Q. How can this state occur?.

A. When your parent activity or parent fragment is already in a stop state. This can happen when your app is trying to move to a new fragment while the user puts your app back to the background.

Q. Apparently it has something to do with a FragmentManager, which I don't use. The stacktrace doesn't show any of my own classes, so I have no idea where this exception occurs and how to prevent it

A. When you attach a ViewPager with Fragments. It internally uses FragmentManager to place fragments.

Q. How to handle it?

A. There are some solutions that others already give but I think you should not use a workaround to avoid this problem. It is a genuine problem and you should handle it gracefully.

You can use the below code to know whether the activity is in a stop state before placing fragments or attaching a viewpager

if(supportFragmentManager.isStateSaved.not()){
        // safe to attach viewpager or fragments
  }

Use childFragmentManager.isStateSaved if inside the parent fragment before placing fragments or attaching viewpager

Legibility answered 6/2 at 10:21 Comment(0)
T
0

Add this in your activity

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}
Transaction answered 3/3, 2016 at 12:35 Comment(0)
V
0

I have also experienced this issue and problem occurs every time when context of your FragmentActivity gets changed (e.g. Screen orientation is changed, etc.). So the best fix for it is to update context from your FragmentActivity.

Vanhorn answered 1/4, 2016 at 10:54 Comment(0)
C
0

I ended up with creating a base fragment and make all fragments in my app extend it

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

Then when I try to show a fragment I use showAllowingStateLoss instead of show

like this:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

I came up to this solution from this PR: https://github.com/googlesamples/easypermissions/pull/170/files

Coakley answered 15/12, 2017 at 17:3 Comment(0)
L
0

Another possible workaround, which I'm not sure if helps in all cases (origin here) :

@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();
        }
    }
}
Lu answered 9/2, 2018 at 8:26 Comment(0)
B
0

I had the exact same problem. It happened because of the destruction of previous activity. when ı backed the previous activity it was destroyed. I put it base activity (WRONG)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
    onCreateDrawerActivity(savedInstanceState);
}

I put it into onStart it was RIGHT

@Override
protected void onStart() {
    super.onStart();
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());

}
Balkanize answered 13/6, 2018 at 6:13 Comment(0)
Q
0

@Gian Gomen In my case call SUPER solves problem. It seems like more correct solution then commitAllowingStateLoss(), because it solves problem, not hide it.

@Override
public void onRequestPermissionsResult(
     final int requestCode,
     @NonNull final String[] permissions, 
     @NonNull final int[] grantResults
) {
        super.onRequestPermissionsResult(requestCode,permissions, grantResults); //<--- Without this line crash 
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    onPermissionGranted(Constants.REQUEST_CODE_PERMISSION_STORAGE);
                }
                break;
        }

Quinacrine answered 13/2, 2019 at 13:50 Comment(0)
C
0

use remove() instead of popup() if state saved.

   private void removeFragment() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    if (fragmentManager.isStateSaved()) {
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null && !fragments.isEmpty()) {
            fragmentManager.beginTransaction().remove(fragments.get(fragments.size() - 1)).commitAllowingStateLoss();
        }
    }
}
Cisneros answered 7/3, 2019 at 9:16 Comment(0)
S
0

I had the same problem but in my case, there were errors in my xml files. I added spaces between buttons and text boxes that were no longer allowed. When I removed them, I did not receive any report of this error.enter image description here

Strickler answered 24/9, 2023 at 17:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.