49

Implementing an app where the user can log in I have the following situation: If the user is logged in perform the action else start the login activity for result and if the result is Activity.RESULT_OK do the action.

My problem is that the action to perfom is to show a DialogFragment, but calling

DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
newFragment.show(ft, "dialog")

in the onActivityResult callback throws an exception:

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

So how can I solve this? I'm thinking in raising a flag there and show the dialog in the onResume but I see this solution a little dirty

Edit: Added more code (Im following this example for showing the DialogFragment

When the action is requested by the user:

... 
if (!user.isLogged()){
 startActivityForResult(new Intent(cnt, Login.class), REQUEST_LOGIN_FOR_COMMENT);
}

In the same fragment

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_LOGIN_FOR_COMMENT && resultCode == Activity.RESULT_OK) {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        DialogFragment newFragment = MyDialogFragment.newInstance();
        newFragment.show(ft, "dialog")
    }
}

And if the user logs in the Login activity calls;

setResult(Activity.RESULT_OK);
finish();
Addev
  • 31,819
  • 51
  • 183
  • 302
  • i think you should post the whole code. Seems like you are trying to show dialog after onpause – nandeesh Aug 24 '12 at 07:57
  • 1
    Check http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html to understand why this is happening – Maragues Sep 18 '13 at 13:44

8 Answers8

102

Best thing I've come up with is to not use .show() but rather do this.

CheckinSuccessDialog dialog = new CheckinSuccessDialog();
//dialog.show(getSupportFragmentManager(), null);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(dialog, null);
ft.commitAllowingStateLoss();
Nathan Schwermann
  • 31,285
  • 16
  • 80
  • 91
  • 2
    Although it works for me, I feel unsafe cause we left out 2 variables : mDismissed = false; mShownByMe = true; The above variables are being modified in original show function. – Cheok Yan Cheng May 31 '13 at 14:11
  • mDismissed and mShowByMe seem to be variables specifically to track if the dialog was shown/hidden by outside calls. In other words they compensate for exactly this kind of thing. – Vince Blas Nov 05 '13 at 23:01
  • How you can "remove" the dialog – Javier Nov 26 '13 at 23:40
  • 33
    have you tried to call super.onActivityResult() at the first line of activityOnResult? – letroll Jul 15 '14 at 08:53
  • yeah uhm but..... WHY?! what is Android doing wrong here (or is it)? Why do we need this workaround? schwiz do you know the reason? – avalancha Sep 24 '15 at 11:46
  • 21
    Calling `super.onActivityResult()` fixed it for me, try this before doing the workaround. – Sakiboy Jun 10 '16 at 21:59
  • @Sakiboy awesome, you should put that as a separate answer! Do you have any idea why it works and if it's safe? – TWiStErRob Aug 14 '16 at 11:37
  • `super.onActivityResult()` allows Android to do its inner state change and state loss is no longer possible. This is why it works. That should be the proper answer. – Janeks Bergs Sep 09 '20 at 18:38
14

Here is the workaround that works fine for me.

private void alert(final String message) {
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
        public void run() {
            AlertDialogFragment alertDialogFragment = AlertDialogFragment.newInstance(message);
            alertDialogFragment.show(getFragmentManager(), ALERT_DIALOG_FRAGMENT);
        }
    });        
}
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
4

If using a DialogFragment, the only thing that worked for me was to dismiss the Fragment with dissmissAllowingStateLoss()

GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61
4

I simply check whether an activity is being destroyed.

if (!getActivity().isFinishing()) {
    DialogFragment fragment = MyFragment.newInstance();
    fragment.show(getActivity().getSupportFragmentManager(), MyFragment.TAG);
}

See also https://stackoverflow.com/a/41813953/2914140. In DialogFragment write:

override fun show(manager: FragmentManager?, tag: String?) {
    try {
        val ft = manager?.beginTransaction()
        ft?.add(this, tag)
        ft?.commitAllowingStateLoss()
    } catch (ignored: IllegalStateException) {
    }
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
2

Override show(Fragment manager, String tag) function with allowing state lose and change mDismissed = false; mShownByMe = true; from origibal function by reflection:

public class DialogParent extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            Field mDismissed = DialogFragment.class.getDeclaredField("mDismissed");
            Field mShownByMe = DialogFragment.class.getDeclaredField("mShownByMe");
            mDismissed.setAccessible(true);
            mShownByMe.setAccessible(true);
            mDismissed.setBoolean(this, false);
            mShownByMe.setBoolean(this, true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commitAllowingStateLoss();
    }
}
georgehardcore
  • 975
  • 14
  • 12
  • 1
    You could likewise surround the parents show method with a try-catch, where you in the catch add the dialog and use your transaction calling `commitAllowingStateLoss()`. This way you don't need any reflection. – SimonSimCity Dec 13 '16 at 06:58
1

You should call super.onActivityResult() before showing the dialog

Gil SH
  • 3,789
  • 1
  • 27
  • 25
0

This real works.

CheckinSuccessDialog dialog = new CheckinSuccessDialog();
//dialog.show(getSupportFragmentManager(), null);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(dialog, null);
ft.commitAllowingStateLoss();

But but still bad, because got error “Activity has been destroyed”

ava.lang.IllegalStateException: Activity has been destroyed fragmentTransaction.commitAllowingStateLoss();

So my solution is add check if (!isFinishing()&&!isDestroyed())

CheckinSuccessDialog fragment = CheckinSuccessDialog.newInstance();

     if (fragment instanceof DialogFragment) {
                DialogFragment dialog = (DialogFragment) fragment;
                if (!dialog.isAdded()) {
                    fragmentTransaction.add(dialog, 
                          CheckinSuccessDialog.class.getName());
                    if (!isFinishing()&&!isDestroyed()) {
                        fragmentTransaction.commitAllowingStateLoss();
                    }
                }

on dismiss:

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        Fragment fragment = getSupportFragmentManager().findFragmentByTag(CheckinSuccessDialog.class.getName());
        if (fragment != null && fragment instanceof DialogFragment) {
            DialogFragment dialog = (DialogFragment) fragment;
            dialog.dismiss();
            if (!isFinishing()&&!isDestroyed()) {
                fragmentTransaction.commitAllowingStateLoss();
            }
        }
Serg Burlaka
  • 2,351
  • 24
  • 35
0

This exception is thrown whenever a FragmentTrasaction is commited after FragmentManager has saved its state. The easy and clean way is to check if FragmentManager has already saved state before showing a DialogFragment.

if(!getSupportFragmentManager.isStateSaved()) {
    MyDialogFragment dialogFragment = new MyDialogFragment()
    dialogFragment.show(getSupportFragmentManager, TAG);
}
jimmy0251
  • 16,293
  • 10
  • 36
  • 39