There are numerous posts about how to handle a configuration change during an AsyncTask, but none I have found give a clear solution regarding apps that are in background (onPause()) when an AsyncTask finishes and tries to dismiss a DialogFragment (compatibility library).
Here is the problem, if I have an AsyncTask running that should dismiss a DialogFragment in onPostExecute(), I get an IllegalStateException if the app is in the background when it tries to dismiss the DialogFragment.
private static class SomeTask extends AsyncTask<Void, Void, Boolean> {
public SomeTask(SomeActivity tActivity)
{
mActivity = tActivity;
}
private SomeActivity mActivity;
/** Set the view during/after config change */
public void setView(Activity tActivity) {
mActivity tActivity;
}
@Override
protected Boolean doInBackground(Void... tParams) {
try {
//simulate some time consuming process
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException ignore) {}
return true;
}
@Override
protected void onPostExecute(Boolean tRouteFound) {
mActivity.dismissSomeDialog();
}
}
The Activity looks like this:
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class SomeActivity extends FragmentActivity {
public void someMethod() {
...
displaySomeDialog();
new SomeTask(this).execute();
...
}
public void displaySomeDialog() {
DialogFragment someDialog = new SomeDialogFragment();
someDialog.show(getFragmentManager(), "dialog");
}
public void dismissSomeDialog() {
SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
someDialog.dismiss();
}
....
}
Works fine UNLESS the app switches to background while SomeTask is still running. In that case, when SomeTask tries to dismissSomeDialog(), I get an IllegalStateException.
05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
All of the posts I've seen seem to point in some kludgy direction with elaborate workarounds. Isn't there some android way of handling this? If it were a Dialog instead of a DialogFragment, then the Activity's dismissDialog() would handle it correctly. If it were a real DialogFragment instead of one from the ACP, then dismissAllowingStateLoss() would handle it. Isn't there something like this for the ACP version of DialogFragment?
tl;dr: The correct way to close a DialogFragment is to use dismiss() directly on the DialogFragment. Control of the dialog (deciding when to show, hide, dismiss it) should be done through the API here, not with direct calls on the dialog.
Showing the DialogFragment Instead, use the show() method to display your dialog. You can pass a reference to a FragmentManager and a String to use as a FragmentTransaction tag.
Android DialogFragments. DialogFragment is a utility class which extends the Fragment class. It is a part of the v4 support library and is used to display an overlay modal window within an activity that floats on top of the rest of the content. Essentially a DialogFragment displays a Dialog but inside a Fragment.
Fragments are saved as part of each Activity's state, so performing transactions after onSaveInstanceState()
has been called technically doesn't make sense.
You definitely don't want to use commitAllowingStateLoss()
to avoid the exception in this case. Consider this scenario as an example:
AsyncTask
. The AsyncTask
shows a DialogFragment
in onPreExecute()
and starts executing its task on a background thread.Activity
is stopped and forced into the background. The system decides that the device is pretty low on memory so it decides that it should also destroy the Activity
too.AsyncTask
completes and onPostExecute()
is called. Inside onPostExecute()
you dismiss the DialogFragment
using commitAllowingStateLoss()
to avoid the exception.Activity
. The FragmentManager
will restore the state of its fragments based on the Activity
's saved state. The saved state doesn't know about anything after onSaveInstanceState()
has been called, so the request to dismiss the DialogFragment
will not be remembered and the DialogFragment
will be restored even though the AsyncTask
has already completed.Because of weird bugs like these that can occasionally happen, it's usually not a good idea to use commitAllowingStateLoss()
to avoid this exception. Because the AsyncTask
callback methods (which are called in response to a background thread finishing its work) have absolutely nothing to do with the Activity
lifecycle methods (which are invoked by the system server process in response to system-wide external events, such as the device falling asleep, or memory running low), handling these situations require you to do a little extra work. Of course, these bugs are extremely rare, and protecting your app against them will often not be the difference between a 1 star rating and a 5 star rating on the play store... but it is still something to be aware of.
Hopefully that made at least some sense. Also, note that Dialog
s also exist as part of the Activity
s state, so although using a plain old Dialog
might avoid the exception, you would essentially have the same problem (i.e. dismissing the Dialog
wouldn't be remembered when the Activity
's state is later restored).
To be frank, the best solution would be to avoid showing a dialog throughout the duration of the AsyncTask
. A much more user-friendly solution would be to show a indeterminate progress spinner in the ActionBar
(like the G+ and Gmail apps, for example). Causing major shifts in the user interface in response to asynchronous callbacks is bad for the user experience because it is unexpected and abruptly yanks the user out of what they are doing.
See this blog post on the subject for more information.
To get around the illegal state exception issue and essentially implement a dismissAllowingStateLoss() can be done using the following.
getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();
This should solve the issue without the hacky code. The same can also be applied for show if you have threads communicating through a handler with the UI thread using dialog.show(); Which can cause an illegal state exception as well
getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();
getFragmentManager()
with
getSupportFragmentManager()
The Activity should assume that the async task may not complete and that it will not perform onPostExecute(). Whatever UI action (ie, spinner, ideally not a dialog) is started to notify the user of the async operation, should have provisions to stop automatically either on a timeout or by tracking state and checking in onRestore/onResume type lifecycle events to ensure the UI is updated properly. Services may also be worth investigating.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With