11

I get this error whenever I have my main activity with a fragment loaded and the user starts a new activity, switches the orientation of the device and comes back to main activity.

@Override
public void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.home_layout);
    super.onCreate(savedInstanceState);
    fragmentManager = getSupportFragmentManager();
    fragment = fragmentManager.findFragmentById(R.id.layFragment);

    initialize();
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    setContentView(R.layout.home_layout);
    initialize();
    super.onConfigurationChanged(newConfig);
}

private void initialize() {
    layStatus = (LinearLayout) findViewById(R.id.layStatus);
    txtStatus = (TextView) findViewById(R.id.txtStatus);
    ....
    handleFragments(lastFragmentId);
}

public void handleFragments(int fragmentId) {
        if (fragment == null) {
            FragmentTransaction ft = fragmentManager.beginTransaction();
            if (fragmentId==someFragmentId){
                ft.replace(R.id.layFragment, new FragmentSomeFragment());
            }
            else
            ....

            ft.commit();
        }
}

In my android manifest, the activity is declared as:

 <activity
        android:name=".HomeActivity"
        android:configChanges="keyboardHidden|orientation" />
<activity

In another questions here on SO, I have found that this may be caused by a bug in Support library do I added without any luck:

// needed as a workaround for a bug in the Support library
@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

My app runs from android 2.2 and I'm using the android-support-v4.jar Support library for fragments.

The log looks like:

07-27 11:56:20.399: E/AndroidRuntime(16021): FATAL EXCEPTION: main
07-27 11:56:20.399: E/AndroidRuntime(16021): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.handleFragments(HomeActivity.java:341)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.initialize(HomeActivity.java:128)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.onConfigurationChanged(HomeActivity.java:153)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.performConfigurationChanged(ActivityThread.java:3618)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.handleActivityConfigurationChanged(ActivityThread.java:3771)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1328)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.os.Handler.dispatchMessage(Handler.java:99)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.os.Looper.loop(Looper.java:137)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.main(ActivityThread.java:4745)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at java.lang.reflect.Method.invokeNative(Native Method)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at java.lang.reflect.Method.invoke(Method.java:511)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at dalvik.system.NativeStart.main(Native Method)
Alin
  • 14,809
  • 40
  • 129
  • 218

4 Answers4

16

First, as I can see, you want to handle configuration changes yourself. In order to let things to work properly with API Level 13+, you have to add one more value to configChanges parameter, as described here.

Next, when the user leave your main activity, onSaveInstanceState and onPause methods are called for it. When the user rotates device and come back to your main activity. onConfigurationChanged method is called before onResume(). So, your activity is still paused and you cannot perform FragmentTransaction.

Further more, if we take a look into source code we can see the following comment for onResume method:

Dispatch onResume() to fragments. Note that for better inter-operation with older versions of the platform, at the point of this call the fragments attached to the activity are not resumed. This means that in some cases the previous state may still be saved, not allowing fragment transactions that modify the state. To correctly interact with fragments in their proper state, you should instead override {@link #onResumeFragments()}.

So, the right place to manipulate fragments in your activity is overriding onResumeFragments method, as we can read in the comment for this method in source code:

This is the fragment-orientated version of {@link #onResume()} that you can override to perform operations in the Activity at the same point where its fragments are resumed. Be sure to always call through to the super-class.

protected void onResumeFragments() {
    super.onResumeFragments();

    // YOUR STUFF IS HERE
}
Community
  • 1
  • 1
StenaviN
  • 3,687
  • 24
  • 34
  • I can't use the onResumeFragments as it isn't available in my Activity. Maybe because I am building against SDK 2.2 ? I tried to add handleFragments() in onResume of the activity but the result is still the same. Any ideas ? – Alin Jul 27 '12 at 10:25
  • 2
    @Alin, If you use fragments your activity should be derived from `FragmentActivity` and your project should reference to v4 compatibility library. [onResumeFragments](http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResumeFragments()) is protected method of `FragmentActivity` and you can override it! – StenaviN Jul 27 '12 at 13:08
  • You are right I must have wrote it wrong, I tried again and now I have onResumeFragments(). The scenario I presented before seems to work now: onResumeFragments I call the handleFragments() and no error is trown and the layout of the fragment changed according to current orientation. However, when I am in main activity with a fragment on screen and change orientation, my handleFragmens doesn't get called anywhere so I remain with an empty place where the fragment should be. So... how should I handle handleFragment in onConfigurationChanged ? – Alin Jul 27 '12 at 13:56
  • @Alin, as I can see, you replace your fragments depending on current orientation. So, what is the reason to handle orientation changes yourself? @Alin, yes, when you rotate device being on your main fragment, `onPause`, `onResume`, `onPostResume` and `onResumeFragments` methods will not be called because you said Android: "I'll take care of configuration changes myself!". I suggest you to get rid of `configChanges` parameter in `AndroidManifest.xml` and set new layout in `onCreate` method. – StenaviN Jul 27 '12 at 14:06
  • 1
    Actually I think I figured it out: if I make a flag within onPause and know the app is paused, then don't use handleFragments() within onConfigurationChanged. Now it seems to work, will make some further testing – Alin Jul 27 '12 at 14:09
  • In the end the solution with onResumeFragments combined with onPause flag worked just fine. However, as you said, there is no need to take care of configChanges. So I just let the activity to be recreated as the important values are stored in a global class and I can easily recreate the app status. Thanks – Alin Jul 27 '12 at 15:16
  • @StenaviN can you share how/where you identified the Android source code examples? (ie: where might that code be for me to review in my GNU/Linux eclipse environment?) – donfede Jan 25 '13 at 23:42
  • Dear god, you don't know how many hours I've wasted trying to figure out why my replace() wasn't working. Hours just to change onResume into onResumeFragments. Thank you! – rnystrom Mar 26 '13 at 19:01
6

If as StenaviN suggests onConfigurationChange() is being returned before resume() on coming back to your activity:

This is the lifecycle:

onCreate()
onResume()
// Move away from you're Activity
onPause()
// Move back to your Activity
onConfigurationChange()
onResume()

But the important part is this:

If you resume an Activity or you change the Orientation of an Activity your Fragments will be fine! You don't need to replace the old one with a new copy and in fact you shouldn't! If you simply remove this line you won't have a problem:

handleFragments(lastFragmentId);

however If you're doing this because you need your Fragment to load a new layout resource (layout/frag.xml => layout-land/frag.xml) then you're going to need to do something like this:

boolean mResumed = false;
onPause() {
    mResumed = false;
}

onResume() {
    mResumed = true;
} 

...

    if(mResumed) handleFragments(lastFragmentId);
Community
  • 1
  • 1
Graeme
  • 25,714
  • 24
  • 124
  • 186
  • @Greame, as I mentioned in my first answer, fragments are not resumed yet when activity's `onResume` method finished. And you still cannot perform fragment transactions. Take a look into this file `\extras\android\compatibility\v4\src\java\android\support\v4\app\FragmentActivity.java`. You can perform fragment transactions in either `onPostResume` or `onResumeFragments` method, calling `super` implementation before. – StenaviN Jul 27 '12 at 13:14
  • I don't suggested calling `FragmentTransaction.commit()` in any other lifecycle state than "resumed" (aka running), I suggest calling it only when a flag indicating the Activity is in the correct state. – Graeme Jul 27 '12 at 13:36
  • I understood you now. But, what is the sense to handle orientation yourself if you want to replace fragments on each orientation changes? Why don't let the system handle orientation changes, removing `configChanges` parameter and just set right layout and instantiate correct fragments in `onCreate`? – StenaviN Jul 27 '12 at 14:14
  • 1
    I agree - if you want to use different resources you should probably try your best to not override the config change. – Graeme Jul 27 '12 at 14:22
  • yes, I got rid of taking care of config changes and now it works great. I just had to make sure the important values are in a global class so I could easily recreate the status of the app Thanks Graeme. – Alin Jul 27 '12 at 15:17
  • @Graeme Regarding: `If you resume an Activity or you change the Orientation of an Activity your Fragments will be fine!` What if the fragment has different layouts based on screensize/orientation...in this case you will have to replace the fragment... – tread Mar 05 '15 at 07:13
1

Are you using the latest version of the support-v4 library? It fixed my similar problem.

Stark
  • 2,581
  • 2
  • 17
  • 20
1

If you are stuck to the version r7 of support library (for instance because you are using maven and desesperatly waiting for an update.. ;) ), then you can use onPostResume to avoid this problem. If your version is r11 or above, then you can switch to onResumeFragements.

Snicolas
  • 37,840
  • 15
  • 114
  • 173