Android: Dealing with "IllegalStateException: Can not perform this action after onSaveInstanceState"

This one baffled me for a while. I've only noticed this recently on KitKat 4.4 while transitioning some old apps to use fragments and this exception was put onto the table, but it's definitely a good one to have around.

There are a lot of posts asking how to fix this online. One of them suggests filling Bundle outState with some data before calling super.onSaveInstanceState(). I'm not sure what the mentality behind that is, but it doesn't work (even though it's the selected solution). Heck, I'm not even sure if it was tested.

A few of them are even (ignorantly) suggesting you change your FragmentTransaction.commit() call to FragmentTransaction.commitAllowingStateLoss(), and this will only cause you and your users problems in the long run.

172214lzcc8fda3j4qjcj4

This is NOT an ideal solution. You are not fixing the problem, you are hiding it. Don't do it!

Personally, the only place I would only use it is after a bad APK has gone live and you need to get it fixed within the next half-hour. Once the temporary hot-fix is out, take the time to fix it properly and then remove commitAllowingStateLoss().

Why is it happening?

In essence, what happens is the timing of your FragmentActivity vs Fragment/DialogFragment code allows information to be lost because they all share a common onSaveInstanceState() .

There is a great post by Alex Lockwood titled "Fragment Transactions & Activity State Loss" which explains the whole thing a lot better than I ever could.

Fixing it

The exception is a tricky one to trace down, but at least the stack trace will give you an indication of where it was generated.

In my case, I had a "open file" activity which returned a result to the main activity. Within MainActivity.onActivityResult(), I validated the file type and then immediately displayed a DialogFragment if it was an unrecognised format.

The onSaveInstanceInstanceState() was called during the activity switching, and then sometime during the DialogFragment.show() process the exception was triggered.

Code before:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}

switch(requestCode) {
case ACTIVITY_CHOOSE_FILE: {
// ...

if (!checkOk(data)) {
// Calling this from onActivityResult() gives this exception
// Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
// Because Activity result calls DialogFragment.show() from openFile().
openFile(data);
}
}
}
}

Rather than displaying the DialogFragment immediately, I put a slight delay in my onActivityResult() code using a thread in order to prevent the error.

Code after:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}

switch(requestCode) {
case ACTIVITY_CHOOSE_FILE: {
// ...

if (!checkOk(data)) {
// Shift the call to a thread
new Thread(new Runnable() {
@Override
public void run() {
// Then bump it back onto the UI thread
runOnUiThread(new Runnable() {
@Override
public void run() {
openFile(data);
}
});
}
}).start();
}
}
}
}

In effect, the end result is the same and runs fine on all versions of Android without any noticeable delays.

Hate it when small things like this catch you out. These exceptions may be annoying, but they prevent you from randomly losing data by making it painfully obvious that you done goofed.

Sources

Frustration after reading many unhelpful (suggesting to use commitAllowingStateLoss) posts on StackOverflow. Here are some examples:

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog