I was trying to figure out why:
getSupportFragmentManager().beginTransaction().commit();
fails, with
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
in a very basic FragmentActivity class.
So here was my use-case (this will be some pseudo-code and not a complete example, sorry): I had one FragmentActivity with an internal AsyncTask class. Roughly something like this:
public class HelloWorld extends FragmentActivity {
showFragment(Fragment fragment, String name) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
}
private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
protected Void doInBackground(Context... contexts) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
/* meh */
}
}
protected void onPostExecute(Void nothing) {
showFragment(new MyFragment(), "myFragment");
}
}
}
Or basically, 10 seconds after starting the app, it would show another fragment. Sounds simple, right? This seemed to work well too, until I decided to rotate the phone. When I did that, the app would crash upon calling "getSupportFragmentManager()..." with the "Can not perform this action...".
You can't call onBackPressed() when your activity is paused. However, the behavior on a back press is to leave the activity. Just call finish() instead of onBackPressed() . You should make sure in your onBackPressed() 's override that the activity is going to finish.
You call commitAllowingStateLoss() after the application onStop() was called, the application process is killed by the system (low memory) while in background and the user returns back to your application.
After lots of debugging, it turned out that when SlowFragmentShow.onPostExecute()
was called, which called showFragment()
, which in turn called getSupportFragmentManager()
, I received a FragmentManager
that was in an IllegalState
(so arguably the exception I got was correct). I'm still not sure why getSupportFragmentManager()
would ever return an object in such a limbo state, but it did, and I needed to somehow get access to the "correct" FragmentManager
.
So to cut to the chase, I stored the FragmentManager
as a static variable in my HelloWorld FragmentActivity
, which I updated when HelloWorld.onStart()
was called:
public class HelloWorld extends FragmentActivity {
private static FragmentManager fragmentManager;
public void onStart() {
fragmentManager = getSupportFragmentManager();
/* more code here */
}
showFragment(Fragment fragment, String name) {
fragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
}
private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
protected Void doInBackground(Context... contexts) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
/* meh */
}
}
protected void onPostExecute(Void nothing) {
showFragment(new MyFragment(), "myFragment");
}
}
}
And well, that pretty much fixed it. Now I could rotate the phone to my hearts desire, the fragment would still be shown when the AsyncTask was done.
In retrospect, it really seems a bit "oh, of course!", but the design decisions behind Android feels quite "alien" and unusual. I seem to end up with try-catch(Exception)
around pretty much half the code just to prevent it from crashing on a non-fatal error (such as failing to update a text field), and a lot of static variables that needs to be updated upon onStart()
because that seems like the only sane way you can reference Android objects without them being in an IllegalState
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With