getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
Solution 1:
This is the most stupid bug I have encountered so far. I had a Fragment
application working perfectly for API < 11, and Force Closing
on API > 11.
I really couldn't figure out what they changed inside the Activity
lifecycle in the call to saveInstance
, but I here is how I solved this :
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
I just do not make the call to .super()
and everything works great. I hope this will save you some time.
EDIT: after some more research, 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);
}
EDIT2: this may also occur if you are trying to perform a transaction after your Activity
is gone in background. To avoid this you should use commitAllowingStateLoss()
EDIT3: The above solutions were fixing issues in the early support.v4 libraries from what I can remember. But if you still have issues with this you MUST also read @AlexLockwood 's blog : Fragment Transactions & Activity State Loss
Summary from the blog post (but I strongly recommend you to read it) :
-
NEVER
commit()
transactions afteronPause()
on pre-Honeycomb, andonStop()
on post-Honeycomb - Be careful when committing transactions inside
Activity
lifecycle methods. UseonCreate()
,onResumeFragments()
andonPostResume()
- Avoid performing transactions inside asynchronous callback methods
- Use
commitAllowingStateLoss()
only as a last resort
Solution 2:
Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl
class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState
.
Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved()
and dispatch()
.
The way I see it there are some available fixes, depending on what your app is doing and using:
Good ways
Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:
-
For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:
Allows the commit to be executed after an activity's state is saved. 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`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.
-
Just after the transaction is commit (you just called
commit()
), make a call toFragmentManager.executePendingTransactions()
.
Not recommended ways:
-
As Ovidiu Latcu mentioned above, don't call
super.onSaveInstanceState()
. But this means you will lose the whole state of your activity along with fragments state. -
Override
onBackPressed
and in there call onlyfinish()
. This should be OK if you application doesn't use Fragments API; as insuper.onBackPressed
there is a call toFragmentManager#popBackStackImmediate()
. -
If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API
FragmentManagerImpl#noteStateNotSaved()
. But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity
and implicitlyLocalActivityManager
).
Below is the code that uses reflection:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
Cheers!
Solution 3:
Such an exception will occur if you try to perform a fragment transition after your fragment activity's onSaveInstanceState()
gets called.
One reason this can happen, is if you leave an AsyncTask
(or Thread
) running when an activity gets stopped.
Any transitions after onSaveInstanceState()
is called could potentially get lost if the system reclaims the activity for resources and recreates it later.
Solution 4:
Simply call super.onPostResume() before showing your fragment or move your code in onPostResume() method after calling super.onPostResume(). This solve the problem!
Solution 5:
This can also happen when calling dismiss()
on a dialog fragment after the screen has been locked\blanked and the Activity + dialog's instance state has been saved. To get around this call:
dismissAllowingStateLoss()
Literally every single time I'm dismissing a dialog i don't care about it's state anymore anyway, so this is ok to do - you're not actually losing any state.