84

I have the following code in my onActivityResult for a fragment of mine:

onActivityResult(int requestCode, int resultCode, Intent data){
   //other code
   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);
   // other code
}

However, I'm getting the following error:

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState   

Anybody know what's going on, or how I can fix this? I should note I'm using the Android Support Package.

Kurtis Nusbaum
  • 30,445
  • 13
  • 78
  • 102

17 Answers17

76

If you use Android support library, onResume method isn't the right place, where to play with fragments. You should do it in onResumeFragments method, see onResume method description: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

So the correct code from my point of view should be:

private boolean mShowDialog = false;

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

  // remember that dialog should be shown
  mShowDialog = true;
}

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

  // play with fragments here
  if (mShowDialog) {
    mShowDialog = false;

    // Show only if is necessary, otherwise FragmentManager will take care
    if (getSupportFragmentManager().findFragmentByTag(PROG_DIALOG_TAG) == null) {
      new ProgressFragment().show(getSupportFragmentManager(), PROG_DIALOG_TAG);
    }
  }
}
Arcao
  • 1,391
  • 12
  • 16
  • 13
    +1, this is the correct answer. Note that `onResumeFragments()` doesn't exist in the `Activity` class. If you are using a basic `Activity`, you should use `onPostResume()` instead. – Alex Lockwood Aug 20 '13 at 23:26
  • 3
    Before implementing this solution, please [read this](http://stackoverflow.com/a/30429551/253704) to see why it's a hack. There is a much simpler solution hidden in the comments of another solution in this question. – twig May 24 '15 at 23:51
  • 1
    Calling super.onActivityResult doesn't prevent IllegalStateException, so isn't fix of a subj. problem – demaksee Oct 19 '15 at 12:29
  • 2
    This question is the first hit on google for this problem, but the accepted answer is not the best one in my opinion. This answer should be accepted instead: http://stackoverflow.com/a/30429551/1226020 – JHH Mar 16 '17 at 14:26
28

The comment left by @Natix is a quick one-liner that some people may have removed.

The simplest solution to this problem is to call super.onActivityResult() BEFORE running your own code. This works regardless if you're using the support library or not and maintains behavioural consistency in your Activity.

There is:

The more I read into this the more insane hacks I've seen.

If you're still running into issues, then the one by Alex Lockwood is the one to check.

Community
  • 1
  • 1
twig
  • 4,034
  • 5
  • 37
  • 47
  • What happens if your have inheritance? Calling super.onActivityResult() could be a problem if you want to first run your code before call super, super class could have its own code inside onActivityResult, be careful. – Ricard Jun 13 '19 at 18:03
  • I'm adding the `super.onActivityResult(requestCode, resultCode, data)` before any of my code, it fixed my issue. But when adding inheritance or overriding the default onActivityResult, we should handle the onStart/onResume manually – mochadwi Mar 31 '20 at 09:31
27

EDIT: Not a bug, but more of a deficiency in the fragments framework. The better answer to this question is the one provided by @Arcao above.

---- Original post ----

Actually it's a known bug with the support package (edit: not actually a bug. see @alex-lockwood's comment). A posted work around in the comments of the bug report is to modify the source of the DialogFragment like so:

public int show(FragmentTransaction transaction, String tag) {
    return show(transaction, tag, false);
}


public int show(FragmentTransaction transaction, String tag, boolean allowStateLoss) {
    transaction.add(this, tag);
    mRemoved = false;
    mBackStackId = allowStateLoss ? transaction.commitAllowingStateLoss() : transaction.commit();
    return mBackStackId;
}

Note this is a giant hack. The way I actually did it was just make my own dialog fragment that I could register with from the original fragment. When that other dialog fragment did things (like be dismissed), it told any listeners that it was going away. I did it like this:

public static class PlayerPasswordFragment extends DialogFragment{

 Player toJoin;
 EditText passwordEdit;
 Button okButton;
 PlayerListFragment playerListFragment = null;

 public void onCreate(Bundle icicle){
   super.onCreate(icicle);
   toJoin = Player.unbundle(getArguments());
   Log.d(TAG, "Player id in PasswordFragment: " + toJoin.getId());
 }

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle){
     View v = inflater.inflate(R.layout.player_password, container, false);
     passwordEdit = (EditText)v.findViewById(R.id.player_password_edit);
     okButton = (Button)v.findViewById(R.id.ok_button);
     okButton.setOnClickListener(new View.OnClickListener(){
       public void onClick(View v){
         passwordEntered();
       }
     });
     getDialog().setTitle(R.string.password_required);
     return v;
 }

 public void passwordEntered(){
   //TODO handle if they didn't type anything in
   playerListFragment.joinPlayer(toJoin, passwordEdit.getText().toString());
   dismiss();
 }

 public void registerPasswordEnteredListener(PlayerListFragment playerListFragment){
   this.playerListFragment = playerListFragment;
 }

 public void unregisterPasswordEnteredListener(){
   this.playerListFragment = null;
 }
}

So now I have a way to notify the PlayerListFragment when things happen. Note that its very important that you call unregisterPasswordEnteredListener appropriately (in the above case when ever the PlayerListFragment "goes away") otherwise this dialog fragment might try to call functions on the registered listener when that listener doesn't exist any more.

Kurtis Nusbaum
  • 30,445
  • 13
  • 78
  • 102
  • 3
    a solution that does not require copying the source ... just override `show()`, and catch the `IllegalStateException`. – Jeffrey Blattman Nov 05 '12 at 17:15
  • 1
    How to modify the source of the DialogFragment? Or can you post your solution mentioned in the end of your post? – Piotr Ślesarew Nov 23 '12 at 11:34
  • 1
    @PeterSlesarew I posted my (rather specific) solution. – Kurtis Nusbaum Feb 08 '13 at 21:49
  • @KurtisNusbaum you offering to edit DialogFragment class from support library? – Robertas Setkus Jul 05 '13 at 07:56
  • 9
    Gah, this is **not** a bug! The Android framework is throwing the exception on purpose because it is not safe to perform fragment transactions inside `onActivityResult()`! Try this solution instead: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult/18345899#18345899 – Alex Lockwood Aug 20 '13 at 23:25
  • 2
    @AlexLockwood The documentation did not warn about this when this question was asked. Additionally, while your solution looks good _now_, it didn't work back in Apr 2012. `onPostResume` and `onResumeFragments` are both relatively new additions to the support library. – hrnt Jan 08 '14 at 14:25
  • (sorry, onResumeFragments is a new addition but onPostResume is not) – hrnt Jan 08 '14 at 14:39
14

I believe it is an Android bug. Basically Android calls onActivityResult at a wrong point in the activity/fragment lifecycle (before onStart()).

The bug is reported at https://issuetracker.google.com/issues/36929762

I solved it by basically storing the Intent as a parameter I later processed in onResume().

[EDIT] There are nowadays better solutions for this issue that were not available back in 2012. See the other answers.

hrnt
  • 9,882
  • 2
  • 31
  • 38
  • 8
    Actually that's not really a bug. As pointed out in the comments there, it clearly states that `onActivityResult()` is called before `onResume()` – Kurtis Nusbaum Apr 11 '12 at 22:34
  • 2
    Did you read the last comment to the bug? The bug is that onActivityResult() is called before onStart(), not that it is called before onResume(). – hrnt Apr 12 '12 at 06:41
  • Ah yes, that's true as well. Missed that. Although I still believe the other bug report is a little more relevant to my issue. – Kurtis Nusbaum Apr 12 '12 at 16:11
  • It is well-defined when onActivityResult is called. Therefore, it cannot be a bug, even if it may seem inappropriate in some cases. – sstn Nov 02 '12 at 14:32
  • 1
    @sstn, could you elaborate? It is well-defined when onActivityResult is called (=immediately before onResume). Android does not call onActivityResult immediately before onResume. Thus, it is a bug. – hrnt Jan 15 '13 at 15:11
11

EDIT: Yet another option, and possibly the best yet (or, at least what the support library expects...)

If you're using DialogFragments with the Android support library, you should be using a subclass of FragmentActivity. Try the following:

onActivityResult(int requestCode, int resultCode, Intent data) {

   super.onActivityResult(requestCode, resultCode, intent);
   //other code

   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);

   // other code
}

I took a look at the source for FragmentActivity, and it looks like it's calling an internal fragment manager in order to resume fragments without losing state.


I found a solution that's not listed here. I create a Handler, and start the dialog fragment in the Handler. So, editing your code a bit:

onActivityResult(int requestCode, int resultCode, Intent data) {

   //other code

   final FragmentManager manager = getActivity().getSupportFragmentManager();
   Handler handler = new Handler();
   handler.post(new Runnable() {
       public void run() {
           ProgressFragment progFragment = new ProgressFragment();  
           progFragment.show(manager, PROG_DIALOG_TAG);
       }
   }); 

  // other code
}

This seems cleaner and less hacky to me.

Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
Simon Jacobs
  • 127
  • 1
  • 5
  • 5
    Using a Handler to solve this problem only adds a delay, therefore making it *more unlikely* that the problem will happen. But it doesn't guarnatee that the problem will go away! It's a bit like solving race conditions using `Thread#sleep()`. – Alex Lockwood Aug 20 '13 at 23:28
  • 27
    **Calling `super.onActivityResult()` is the simplest working solution there is and it should probably be the accepted answer!** I noticed the missing super call by accident and was pleasantly surprised that adding it just worked. This allowed me to remove one of the old hacks mentioned in this page (saving the dialog into a temporary variable and showing it in `onResume()`). – Natix Nov 06 '13 at 22:15
  • Nice solution. The only problem is that `onActivityResult()` doesn't return any value that indicates whether or not fragments handled the result. – Michael Apr 22 '15 at 07:55
  • Calling super.onActivityResult() doesn't salve IllegalStateException crash in my project – demaksee Oct 19 '15 at 12:35
9

There are two DialogFragment show() methods - show(FragmentManager manager, String tag) and show(FragmentTransaction transaction, String tag).

If you want to use the FragmentManager version of the method (as in the original question), an easy solution is to override this method and use commitAllowingStateLoss:

public class MyDialogFragment extends DialogFragment {

  @Override 
  public void show(FragmentManager manager, String tag) {
      FragmentTransaction ft = manager.beginTransaction();
      ft.add(this, tag);
      ft.commitAllowingStateLoss();
  }

}

Overriding show(FragmentTransaction, String) this way is not as easy because it should also modify some internal variables within the original DialogFragment code, so I wouldn't recommend it - if you want to use that method, then try the suggestions in the accepted answer (or the comment from Jeffrey Blattman).

There is some risk in using commitAllowingStateLoss - the documentation states "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."

gkee
  • 771
  • 1
  • 7
  • 11
4

You cannot show dialog after attached activity called its method onSaveInstanceState(). Obviously, onSaveInstanceState() is called before onActivityResult(). So you should show your dialog in this callback method OnResumeFragment(), you do not need to override DialogFragment's show() method. Hope this will help you.

handrenliang
  • 1,047
  • 1
  • 10
  • 21
3

I came up with a third solution, based partially from hmt's solution. Basically, create an ArrayList of DialogFragments, to be shown upon onResume();

ArrayList<DialogFragment> dialogList=new ArrayList<DialogFragment>();

//Some function, like onActivityResults
{
    DialogFragment dialog=new DialogFragment();
    dialogList.add(dialog);
}


protected void onResume()
{
    super.onResume();
    while (!dialogList.isEmpty())
        dialogList.remove(0).show(getSupportFragmentManager(),"someDialog");
}
PearsonArtPhoto
  • 38,970
  • 17
  • 111
  • 142
3

onActivityResult() executes before onResume(). You need to do your UI in onResume() or later.

Use a boolean or whatever you need to communicate that a result has come back between both of these methods.

... That's it. Simple.

Eurig Jones
  • 8,226
  • 7
  • 52
  • 74
2

I know this was answered quite a while ago.. but there is a much easier way to do this than some of the other answers I saw on here... In my specific case I needed to show a DialogFragment from a fragments onActivityResult() method.

This is my code for handling that, and it works beautifully:

DialogFragment myFrag; //Don't forget to instantiate this
FragmentTransaction trans = getActivity().getSupportFragmentManager().beginTransaction();
trans.add(myFrag, "MyDialogFragmentTag");
trans.commitAllowingStateLoss();

As was mentioned in some of the other posts, committing with state loss can cause problems if you aren't careful... in my case I was simply displaying an error message to the user with a button to close the dialog, so if the state of that is lost it isn't a big deal.

Hope this helps...

Justin
  • 6,564
  • 6
  • 37
  • 34
2

It's an old question but I've solved by the simplest way, I think:

getActivity().runOnUiThread(new Runnable() {
    @Override
        public void run() {
            MsgUtils.toast(getString(R.string.msg_img_saved),
                    getActivity().getApplicationContext());
        }
    });
d4Rk
  • 6,622
  • 5
  • 46
  • 60
LucasBatalha
  • 43
  • 1
  • 8
2

It happens because when #onActivityResult() is called, the parent activity has already call #onSaveInstanceState()

I would use a Runnable to "save" the action (show dialog) on #onActivityResult() to use it later when activity has been ready.

With this approach we make sure the action we want to will always work

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == YOUR_REQUEST_CODE) {
        mRunnable = new Runnable() {
            @Override
            public void run() {
                showDialog();
            }
        };
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

@Override
public void onStart() {
    super.onStart();
    if (mRunnable != null) {
        mRunnable.run();
        mRunnable = null;
    }
}
Ricard
  • 1,223
  • 1
  • 13
  • 17
0

The cleanest solution I found is this:

@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            onActivityResultDelayed(requestCode, resultCode, data);
        }
    });
}

public void onActivityResultDelayed(int requestCode, int resultCode, Intent data) {
    // Move your onActivityResult() code here.
}
fhucho
  • 34,062
  • 40
  • 136
  • 186
0

I got this error while doing .show(getSupportFragmentManager(), "MyDialog"); in activity.

Try .show(getSupportFragmentManager().beginTransaction(), "MyDialog"); first.

If still not working, this post (Show DialogFragment from onActivityResult) helps me to solve the issue.

Community
  • 1
  • 1
Youngjae
  • 24,352
  • 18
  • 113
  • 198
0

Another way:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case Activity.RESULT_OK:
            new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message m) {
                    showErrorDialog(msg);
                    return false;
                }
            }).sendEmptyMessage(0);
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
    }
}


private void showErrorDialog(String msg) {
    // build and show dialog here
}
Maher Abuthraa
  • 17,493
  • 11
  • 81
  • 103
-1

just call super.onActivityResult(requestCode, resultCode, data); before handling the fragment

Tommehh
  • 872
  • 1
  • 17
  • 44
-3

As you all know this problem is because of on onActivityResult() is calling before onstart(),so just call onstart() at start in onActivityResult() like i have done in this code

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}