39

Steps:

  1. Request a permission from Fragment or Activity
  2. Show a DialogFragment from within onRequestPermissionsResult()
  3. java.lang.IllegalStateException is thrown: Can not perform this action after onSaveInstanceState

This doesn't happen when I show dialog after some delay(using postDelayed). According to http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html) on post-Honeycomb devices we CAN commit() between onPause() and onStop() without any STATE LOSS or EXCEPTION. Here is a link to sample project source, log file and issue recorded. https://drive.google.com/folderview?id=0BwvvuYbQTUl6STVSZF9TX2VUeHM&usp=sharing

Also I have opened an issue https://code.google.com/p/android/issues/detail?id=190966 but it was marked as WorkingAsIntended and they suggest to just catch exception. But this doesn't solve the issue. I know other ways to solve it, but isn't this android bug?

UPDATE Status fo the bug is again "assigned". Hope it will be fixed soon. My temp solution is

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // do your fragment transaction here
    }
}, 200);
lalibi
  • 3,057
  • 3
  • 33
  • 41
android_dev
  • 3,886
  • 1
  • 33
  • 52
  • are you using support dialogfragment ?? and are you using fragment activity ? – dex Oct 21 '15 at 16:02
  • @dex yes,support dialog fragment and appcompatactivity – android_dev Oct 21 '15 at 16:16
  • then there is already logged for the same check this : https://code.google.com/p/android/issues/detail?id=23761 – dex Oct 21 '15 at 16:23
  • @dex this isn't the same. In case of onActivityResult() calling activity is stopped and onSaveInstanceState() is called. Then when you want to show dialog, exception is thrown and it is ok. But in this case calling activity isn't stopped,it is just paused.. – android_dev Oct 21 '15 at 16:34
  • so as soon as you grant permission, onRequestionPemrission result come in this case also app has been auto-restarted by OS. – dex Oct 21 '15 at 16:40
  • 2
    This is causing me lots of trouble as well. Looks like there's an official bug and a group of people complaining that it's still not 'fixed' as it's marked: https://code.google.com/p/android-developer-preview/issues/detail?id=2823 – Matthew Housser Oct 23 '15 at 02:54
  • Please to add your solution as an answer and market to make it clear what is the question and what is the answer .. thanks – Maher Abuthraa May 30 '17 at 06:09

8 Answers8

24

The bug is accepted and will be fixed, however, I strongly disagree with the postDelayed and timer solutions. The best way to do this would be introducing a state flag in the Activity, in which you set in the callback, and use in onResume or similar. For example:

private boolean someFlag;

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// some code checking status
    someFlag = true;
}

And then in onResume:

protected void onResume() {
    if(someFlag == true) {
        doSomething();
        someFlag = false;
    }
}
Kenneth
  • 3,957
  • 8
  • 39
  • 62
  • please, see my comment under "Scott Key" answer. But if postDelayed works fine, why not? – android_dev Dec 29 '15 at 08:52
  • 1
    Yes, you are onto it. The postDelayed-approach works, but it is not very solid. In this case we do know that it's enough to release control of the thread, but in general, what if something happends delaying what you are waiting for? You might not have set a sufficient delay, and your code breaks. You should never rely on magic delays. – Kenneth Dec 29 '15 at 08:54
  • Yes, I know. But for my case it is ok, cause I know that something will not probably happen that will make a delay. I don't want to make my code unreadable, cause I hope android team will fix this soon. – android_dev Dec 29 '15 at 16:18
  • i agree, id rather use a flag in onResume() and not rely on some time interval -- no matter how unlikely it is to occur/not occur – joshkendrick Feb 19 '16 at 16:24
  • While the accepted answer generally does the trick, this one should be accepted as it is reliable all the time. Thanks for the solution, you just saved me 2+ days of headaches over this issue ;) – mjp66 Aug 09 '16 at 08:59
6

I also think this is android bug. I can't believe they marked your issue WorkingAsIntended. Only solution for now is to delay execution of code in onRequestPermissionsResult() until android folks fix this properly.

This is my way of solving this issue if anyone is wondering how to delay execution:

  @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (requestCode == PERMISSION_CODE) {
      if (/* PERMISSION ALLOWED/DENIED */) {
          new Timer().schedule(new TimerTask() {
              @Override public void run() {
                  // EXECUTE ACTIONS (LIKE FRAGMENT TRANSACTION ETC.)
              }
          }, 0);
      }
  }

This essentially delays execution until onRequestPermissionsResult() finishes so we don't get java.lang.IllegalStateException. This works in my app.

appersiano
  • 2,670
  • 22
  • 42
ldulcic
  • 61
  • 1
  • 5
4

try something like this:

// ...
private Runnable mRunnable;

@Override
public void onResume() {
   super.onResume();

   if (mRunnable != null) {
       mRunnable.run();
       mRunnable = null;
   }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);

   if (/* PERMISSION_DENIED */) {
      mRunnable = /* new Runnable which show dialogFragment*/;
   }
}
Scott Key
  • 119
  • 5
  • The solution is to keep a flag and call show dialog in onPostResume..That's not the question..This seems to be a bug.. – android_dev Oct 23 '15 at 07:31
4

This is a bug in Android https://code.google.com/p/android/issues/detail?id=190966&q=label%3AReportedBy-Developer&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

So, for now, workarounds are necessary. You can use @ldulcic's solution. Or you can use Handler's postDelay method. Second option is preferred.

4

Since you are using a DialogFragment, you should keep a flag or state and than show your dialog in onPostResume. You should do this in onPostResume rather than onResume or other life cycle methods.

As the documentation clearly states, if you commit transactions in Activity life cycle methods other than onPostResume or onResumeFragments (for FragmentActivity), in some cases the method can be called before the activity's state has been fully restored.

So, if you show your dialog onPostResume you'll be fine.

CanC
  • 3,295
  • 1
  • 14
  • 15
  • You are right :) please, see my comment under "Scott Key" answer. Hope this will be fixed soon and we won't have to use custom solutions – android_dev Jan 07 '16 at 11:07
2

I got around this in what I believe to be a much more deterministic way. Rather than using timers, I basically queue the result off until Activity.onResumeFragments() (or you could do it in Activity.onResume() if you're not using fragments). I did onResumeFragments() because I also route the result off to the specific fragment that requested the permission so I need to be sure the fragments are ready.

Here's the onRequestPermissionsResult():

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    // This is super ugly, but google has a bug that they are calling this method BEFORE
    // onResume... which screws up fragment lifecycle big time.  So work around it.  But also
    // be robust enough to still work if/when they fix the bug.
    if (mFragmentsResumed) {
        routePermissionsResult(requestCode, permissions, grantResults);
    } else {
        mQueuedPermissionGrantResults = grantResults;
        mQueuedPermissionRequestCode = requestCode;
        mQueuedPermissions = permissions;
    }
}

Then here's onResumeFragments():

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

    mFragmentsResumed = true;

    if (mQueuedPermissionGrantResults != null) {
        routePermissionsResult(mQueuedPermissionRequestCode, mQueuedPermissions,
                mQueuedPermissionGrantResults);
    }

And for completeness, here's onPause(), which clears the mFragmentsResumed flag:

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

    mFragmentsResumed = false;
}

I used the mFragmentsResumed flag because I don't want this code to stop working if and when google fixes the bug (changing the order of the lifecycle invocations would make the code not work if I was simply setting the queued variables in onRequestPermissionsResult but onResumeFragments was called before this).

pdub
  • 165
  • 1
  • 8
  • Implemented this and it seems to work like a charm. Clean solution to an ugly problem. Nice that you can process the onRequestPermissionsResult in routePermissionsResult method the same way you would when it is originally called. – lejonl Sep 19 '16 at 18:45
0

As explained in one of the comments in the issue, this only happens when using the support lib's DialogFragment (i.e., android.support.v4.app.DialogFragment). You can change your code to use the "native" DialogFragment (android.app.DialogFragment) and it will work fine.

I know in some cases using the native DialogFragment is not possible, but in this case, given that you are referring to runtime permissions (a feature which is only available in the latest versions of Android), you are most likely able to do this. I had this exact issue and I could fix it this way.

Franco
  • 2,711
  • 2
  • 20
  • 27
0

I also solved it with a Handler but I was able to avoid the use of a timer setting its Looper to MainLooper. So it will be run once the view is rendered:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        // do your fragment transaction here
    }
});
Rubén Viguera
  • 3,277
  • 1
  • 17
  • 31