"Failure Delivering Result " - onActivityForResult
Asked Answered
S

5

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)
Serranid answered 28/4, 2013 at 17:39 Comment(1)
how did you call startActivityForResult()Tallbot
I
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).

Inverse answered 20/8, 2013 at 22:20 Comment(24)
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!Ry
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](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. :)Inverse
@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.Entryway
@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).Inverse
@AlexLockwood, yes I use a flag now - I wanted to avoid using a boolean here, but it seems to be unavoidable in some cases.Entryway
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).Misanthropy
Before implementing this solution, please read this to see why it's a hack (along with many others). The real solution is a one line fix.Ted
@Ted 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.Inverse
@AlexLockwood Sorry about that. I missed that line when looking through your solution and lumped your fix along with the other hacks. Updated post accordinglyTed
@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.Tanney
@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?Tanney
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?Vaasa
@Vaasa 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.Inverse
@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 activityVaasa
@AlexLockwood I have same issue with onRequestPermissionsResult code.google.com/p/android/issues/detail?id=190966Eire
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.Spermatozoon
@ThuyTrinh Is there a bug that you know of that confirms this? I wasn't aware of any changes to the fragment lifecycle recently.Inverse
@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.Squiggle
please check this #43618100Kurtzman
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.Lionize
@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.Reliquiae
@AlexLockwood: What about this method? ``` @OnLifecycleEvent(Lifecycle.Event.ON_START) ``` I will use this to detect app resume (not only one activity)Maximilianus
developer.android.com/reference/android/app/… 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...Gastro
developer.android.com/reference/android/support/v4/app/… 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.Gastro
L
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
    }
}
Lionize answered 22/9, 2017 at 20:54 Comment(1)
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.Highpowered
S
-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.

Snifter answered 28/4, 2013 at 17:46 Comment(2)
Thanks. finish() is last thing I am doing. I had the intent code just above it., I moved it up top and same error. I am adding code to answer...Serranid
One more point on last comment. I actually don't see toast. But when I reenter app after crash I am logged in.Serranid
R
-1

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

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

Roxi answered 16/5, 2013 at 3:43 Comment(0)
A
-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();

}
Argonaut answered 4/4, 2017 at 7:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.