76

I have a LoginActivity (User Logs in). It is basically its own Activity that is themed like a dialog (to appear as if a dialog). It appears over a SherlockFragmentActivity. What I want is: If there is a successful login, there should be two FragmentTransaction's to update the view. Here is the code:

In LoginActivity, if successful login,

setResult(1, new Intent());

In SherlockFragmentActivity:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == 1) {
        LoggedStatus = PrefActivity.getUserLoggedInStatus(this);
        FragmentTransaction t = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mFrag = new MasterFragment();
        t.replace(R.id.menu_frame, mFrag);
        t.commit();

        // Set up Main Screen
        FragmentTransaction t2 = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mainFrag = new FeaturedFragment();
        t2.replace(R.id.main_frag, mainFrag);
        t2.commit();
    }
}

It crashes on the first commit, with this LogCat:

E/AndroidRuntime(32072): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
E/AndroidRuntime(32072):    at com.kickinglettuce.rate_this.MainFragmentActivity.onActivityResult(MainFragmentActivity.java:243)
E/AndroidRuntime(32072):    at android.app.Activity.dispatchActivityResult(Activity.java:5293)
E/AndroidRuntime(32072):    at android.app.ActivityThread.deliverResults(ActivityThread.java:3315)
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
TheLettuceMaster
  • 15,594
  • 48
  • 153
  • 259

5 Answers5

300

First of all, you should read my blog post for more information (it talks about why this exception happens and what you can do to prevent it).

Calling commitAllowingStateLoss() is more of a hack than a fix. State loss is bad and should be avoided at all costs. At the time that onActivityResult() is called, the activity/fragment's state may not yet have been restored, and therefore any transactions that happen during this time will be lost as a result. This is a very important bug which must be addressed! (Note that the bug only happens when your Activity is coming back after having been killed by the system... which, depending on how much memory the device has, can sometimes be rare... so this sort of bug is not something that is very easy to catch while testing).

Try moving your transactions into onPostResume() instead (note that onPostResume() is always called after onResume() and onResume() is always called after onActivityResult()):

private boolean mReturningWithResult = false;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mReturningWithResult = true;
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mReturningWithResult) {
        // Commit your transactions here.
    }
    // Reset the boolean flag back to false for next time.
    mReturningWithResult = false;
}

This might seem a little weird, but doing this sort of thing is necessary to ensure that your FragmentTransactions are always committed after the Activity's state has been restored to its original state (onPostResume() is guaranteed to be called after the Activity's state has been restored).

Matt
  • 74,352
  • 26
  • 153
  • 180
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • 4
    In Fragments, I'm using onResume in the absence of onPostResume. I haven't seen the issue again, but perhaps you want to comment on this. PS. Thanks a lot for your insightful posts! – Maragues Sep 18 '13 at 14:17
  • 27
    Yes, doing this in `Fragment#onResume()` is fine. This is because `FragmentActivity#onPostResume()` calls `FragmentActivity#onResumeFragments()`, which calls `FragmentManager#dispatchResume()`, which calls `Fragment#onResume()` for each of the activity's fragments. Therefore, `Fragment#onResume()` is called after `FragmentActivity#onPostResume()` so there won't be a problem (you can check out the [source code](http://goo.gl/Lo1Z1T ) for each respective class to verify this for yourself... or you can just just me :P). And thanks! Glad you thought they were insightful. :) – Alex Lockwood Sep 18 '13 at 21:13
  • 1
    @Alex, If the dialogfragment makes a network call, and the Home button is pressed before the dialogfragment gets the async response, invoking this.dismiss() within the async response will cause a state loss exception. In this scenario, when should dismiss() be invoked so that there is no state loss? Note that request and response are within the dialogfragment, not within the activity. – crazy horse Oct 29 '13 at 07:08
  • 2
    @crazyhorse In that case, it might be a good idea to cancel the async task and/or raise a flag to ensure that `dismiss()` isn't called inside `AsyncTask#onPostExecute()` (the dialog will be dismissed automatically by the `FragmentManager` in the case that the activity goes in the background, so there's no need for you to dismiss the dialog yourself after the activity has gone away anyway). – Alex Lockwood Dec 19 '13 at 04:53
  • @AlexLockwood, yes I use a flag now - I wanted to avoid using a boolean here, but it seems to be unavoidable in some cases. – crazy horse Dec 26 '13 at 22:22
  • 1
    Did not know about `onPostResume()`. Fixed my problem (I was creating a dialog fragment in `onResume()` after cancelling a system dialog from a pending intent, which crashed my app). – EpicPandaForce Mar 25 '15 at 10:52
  • Before implementing this solution, please [read this](http://stackoverflow.com/a/30429551/253704) to see why it's a hack (along with many others). The real solution is a one line fix. – twig May 24 '15 at 23:54
  • @twig I do call `super.onActivityResult()` in my answer (and the OP does in the question as well), so I'm not sure how the link you posted solves the problem. – Alex Lockwood May 25 '15 at 02:25
  • @AlexLockwood Sorry about that. I missed that line when looking through your solution and lumped your fix along with the other hacks. Updated post accordingly – twig May 25 '15 at 04:57
  • @AlexLockwood Thanks for the detailed explanation. I had been using `commitAllowingStateLoss` all the while for a 2+ years old app. But, I never observe any strange thing. May I know, if state loss happens, does it happen in the following sequence? history chart fragment commit in onActivityResult -> restore state happen and history chart is whipped out from fragment manager -> onResume called. – Cheok Yan Cheng Jun 17 '15 at 09:35
  • @AlexLockwood Also, may I know is "restoring state" you mentioned in your article, does it has anything to do with `onSaveInstanceState(Bundle)`? As, I usually preserve my variables in `onSaveInstanceState(Bundle)`, and restore them back in `onCreate(Bundle)`. Is "restoring state" referring to what I'm doing in `onCreate(Bundle)`, or it is another thingy? – Cheok Yan Cheng Jun 17 '15 at 09:38
  • Does this imply that you shouldn't really ever do anything (at least reference anything) in `onActivityResult` except set the `resultCode` and `data` to a global variable then do the operations in `onResume`? – ono Sep 14 '15 at 21:33
  • @ono No, there are still plenty of valid ways to override `onActivityResult()`. You just have to be aware that `onActivityResult()` is called before the activity/fragments have restored their saved state. So for example, if you started an activity for a result and then modified a text view's text upon returning in `onActivityResult()`, it's possible that the updated text will be overwritten afterwards when the activity/fragment's view state is restored. – Alex Lockwood Sep 14 '15 at 21:40
  • @AlexLockwood my problem is that I'm trying to set a text in a TextView from the data in the Intent from onActivityResult but my view is null. Only if android removed my activity – ono Sep 14 '15 at 21:43
  • @AlexLockwood I have same issue with onRequestPermissionsResult https://code.google.com/p/android/issues/detail?id=190966 – android_dev Oct 20 '15 at 11:08
  • As of support lib 22.2.1, `onActivityResult` in `Fragment` is invoked after `onStart`. So I think if u wanna show dialog fragment in `Fragment.onActivityResult`, it's safe to invoke `show()` directly without any workaround. – Thuy Trinh Jan 11 '16 at 11:18
  • @ThuyTrinh Is there a bug that you know of that confirms this? I wasn't aware of any changes to the fragment lifecycle recently. – Alex Lockwood Jan 11 '16 at 14:33
  • 1
    @AlexLockwood thank you for this post. In you blog you say that transactions should be commited after Activity#onPostResume. But at the same time you say that they can be commited in Activity#onCreate (which runs even before onResume). I'm sure that I've misunderstood something, so can you explain. – nutella_eater Jan 30 '17 at 14:12
  • please check this http://stackoverflow.com/questions/43617600/illegal-state-exception-in-androidcan-not-perform-this-action-after-onsaveinsta – vm345 Apr 28 '17 at 18:37
  • Just got this error on *Android O (8.0)* with the latest *support lib 26.0.2*. The bug is still present. The workaround in this answer fixes the problem. I got this error after calling `show()` on a `DialogFragment`. – vovahost Sep 22 '17 at 20:42
  • @alexeypolusov My understanding is that it is safe to commit transactions from `Activity#onCreate` because that is guaranteed to happen before any fragment state restoration and the framework dictates this is the right place to override the default restoration of a fragment if you choose. So there is a window between `onCreate` and `onPostResume` where you have to wait for the state restoration to finish before making transactions. – hmac Jun 11 '19 at 21:22
  • @AlexLockwood: What about this method? ``` @OnLifecycleEvent(Lifecycle.Event.ON_START) ``` I will use this to detect app resume (not only one activity) – Huy Nguyen Jan 08 '20 at 15:21
  • 1
    https://developer.android.com/reference/android/app/Activity#onPostResume() says "Applications will generally not implement this method; it is intended for system classes to do final setup after application resume code has run." - are you sure I should use this? My app has to open different fragments on startup under certain conditions. I 've used onResume() of the main activity for a few years now. No problem I just found this thread because I'm looking for a rarely occurring illegal state in onSaveInstanceState() of this activity... – The incredible Jan Jan 22 '20 at 14:17
  • https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume() void onResumeFragments () This is the fragment-orientated version of 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. – The incredible Jan Jan 22 '20 at 14:40
1

This is similar to the @Alex Lockwood answer but using a Runnable:

private Runnable mOnActivityResultTask;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = new Runnable() {
        @Override
        public void run() {
            // Your code here
        }
    }
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mOnActivityResultTask != null) {
        mOnActivityResultTask.run();
        mOnActivityResultTask = null;
    }
}

In case you're running Android 3.0 and above with lambdas, use this:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = () -> {
        // Your code here
    }
}
vovahost
  • 34,185
  • 17
  • 113
  • 116
  • I'd rather go with runnable, since that lets you write the logic close to where it happens. In my case it's coming from rx subscriber. – Fabio Sep 23 '17 at 07:35
-1

your logcat clearly says: "Can not perform this action after onSaveInstanceState" -- your Activity is already dead at that point and could not return any results.

just move:

Intent in = new Intent();
setResult(1, in);

to the place in your Activity where it's still alive, and everything will be fine. and don't forget to finish() your Activity to deliver the result.

Piyush
  • 18,895
  • 5
  • 32
  • 63
lenik
  • 23,228
  • 4
  • 34
  • 43
-1

You can use ft.commitAllowingStateLoss() to solve this problem.

Reason: your ft.commit() method was toggled after onSaveInstanceState.

fawaad
  • 341
  • 6
  • 12
muyiou
  • 679
  • 5
  • 15
-1

In my Case I faced the same issues Because of the Following

public void onBackPressed() {
    super.onBackPressed();
    setIntents();

}


private void setIntents(){
    Intent searchConstaints=new Intent();
    searchConstaints.putExtra("min",20);
    searchConstaints.putExtra("max",80);
    setResult(101,searchConstaints);
    finish();
}

Solved by re-arranging the function Calls within onBackPressed()

public void onBackPressed() {

    setIntents();
    super.onBackPressed();

}
RAJESH KUMAR ARUMUGAM
  • 1,560
  • 21
  • 35