3

Previously, we have no problem displaying the following DialogFragment

enter image description here

// Triggered by button click.
private void openFromCloud() {      
    LoadFromCloudTaskFragment loadFromCloudTaskFragment = new LoadFromCloudTaskFragment();
    FragmentManager fm = this.getSupportFragmentManager();
    fm.beginTransaction().add(loadFromCloudTaskFragment, "loadFromCloudTaskFragment").commit(); 
}

However, if we tend to display the same DialogFragment after pressing OK button on the following Intent, an error will occur.

enter image description here

private void openFromCloud() {      
    startActivityForResult(Utils.getGoogleAccountCredential().newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}

@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    switch (requestCode) {
    case REQUEST_ACCOUNT_PICKER:
        if (resultCode == RESULT_OK && data != null && data.getExtras() != null) {
            String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            if (accountName != null) {
                Utils.getGoogleAccountCredential().setSelectedAccountName(accountName);
                LoadFromCloudTaskFragment loadFromCloudTaskFragment = new LoadFromCloudTaskFragment();
                FragmentManager fm = getSupportFragmentManager();
                fm.beginTransaction().add(loadFromCloudTaskFragment, "loadFromCloudTaskFragment").commit();
            }
        }
    break;
    }
}

Here are the detailed error log

FATAL EXCEPTION: main
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { (has extras) }} to activity {org.yccheok.xxx.gui/org.yccheok.xxx.gui.XXXFragmentActivity}: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.app.ActivityThread.deliverResults(ActivityThread.java:3141)
    at android.app.ActivityThread.handleSendResult(ActivityThread.java:3184)
    at android.app.ActivityThread.access$1100(ActivityThread.java:130)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1243)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4745)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
    at org.yccheok.xxx.gui.XXXFragmentActivity$1.run(XXXFragmentActivity.java:107)
    at android.app.Activity.runOnUiThread(Activity.java:4591)
    at org.yccheok.xxx.gui.XXXFragmentActivity.onActivityResult(XXXFragmentActivity.java:102)
    at android.app.Activity.dispatchActivityResult(Activity.java:5192)
    at android.app.ActivityThread.deliverResults(ActivityThread.java:3137)
    ... 11 more

I can simply "solve" the problem by using commitAllowingStateLoss instead of commit.

fm.beginTransaction().add(loadFromCloudTaskFragment, "loadFromCloudTaskFragment").commitAllowingStateLoss();

I don't really understand the documentation regarding commitAllowingStateLoss.

Like commit() but 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.

This is based on suggestion from getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"

I don't really get the point it is okay for the UI state to change unexpectedly on the user.? May I know what is the possible side effect for using commitAllowingStateLoss? Any steps I can produce such side effect?

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • See this [**blog post**](http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html) for more information about why this exception occurs. – Alex Lockwood Aug 20 '13 at 22:37

3 Answers3

5

The only thing I can think of, is kind of "race condition" event.

Imagine a situation, when device is being rotated just before your commitAllowingStateLoss() call. AFAIK, the following occurs:

  • onSaveInstanceState() callback(activity store it's state with no fragment present at the moment(since you haven't committed anything yet)
  • commitAllowingStateLoss is executed adding fragment to the activity
  • Activity is recreated, restoring it's state to the moment, when there were no your fragment added

In my opinion, it cause hardly predictable situations, such as:

  • java.lang.IllegalStateException: Failure saving state: FragmentB has target not in fragment manager: FragmentA if you are using Fragment.setTargetFragment() for any reason
  • your fragment can simply be missing from the view

Anyway, I'm not 100% sure about that, but I'm having lots of unexpected java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState exceptions in my app, and trying to find a solution too.

Mykhailo Gaidai
  • 3,130
  • 1
  • 21
  • 23
  • take a look [here](http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html) – Mykhailo Gaidai Jan 16 '14 at 09:06
  • nah anh I found out what the issue was...it was because the fragment which was trying to be had a target fragment which wasn't in the backstack – Hades Jan 17 '14 at 04:21
2

Probably you don't have this problem anymore. I got a similar problem. I solved put fragment transition code in onResume function. I have put a flag to indicate if result was ok. If it's true then inside onResume method I did transition to other Fragment.

user1321759
  • 1,734
  • 2
  • 14
  • 11
  • Yes, I also believe that this is the correct way to do it. There's no need to use `commitAllowingStateLoss()` in this case either. :) – Alex Lockwood Aug 20 '13 at 22:42
  • If the suggestion we are talking about here isn't clear... this [**answer**](http://stackoverflow.com/q/16265733/844882) uses this approach and solves the problem nicely. – Alex Lockwood Aug 20 '13 at 22:52
0

You can see the follow code in Activity.java

 protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

key codes ===>mFragments.saveAllState()

if you commit after onSaveInstanceState then the state of the fragment will not be saved

TheOne_su
  • 669
  • 6
  • 7