How Can You Handle Dismissing A Dialogfragment (compatibility Lib) Upon Completion Of An Asynctask
Solution 1:
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:
- The Activity executes an
AsyncTask
. TheAsyncTask
shows aDialogFragment
inonPreExecute()
and starts executing its task on a background thread. - The user clicks "Home" and the
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 theActivity
too. - The
AsyncTask
completes andonPostExecute()
is called. InsideonPostExecute()
you dismiss theDialogFragment
usingcommitAllowingStateLoss()
to avoid the exception. - The user navigates back to the
Activity
. TheFragmentManager
will restore the state of its fragments based on theActivity
's saved state. The saved state doesn't know about anything afteronSaveInstanceState()
has been called, so the request to dismiss theDialogFragment
will not be remembered and theDialogFragment
will be restored even though theAsyncTask
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.
Solution 2:
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();
@joneswah is correct, given the posters question. If you are using the support library, replace
getFragmentManager()
with
getSupportFragmentManager()
For future Googlers: @Alex Lockwood raises good and valid concerns with this solution. The solution does solve the error and will work in most cases, but hints that there are issues with the approach in the original question, from a UX perspective.
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.
Solution 3:
You should cancel your AsyncTask in onPause() if the onPostExecute() is going to update the UI. You shouldn't try to update the UI while your activity has been paused.
Eg. in your onPause():
if (task != null) task.cancel(true);
If you want the changes from the task to persist to the next time, then store the data/changes in doInBackground() and then update the UI when your activity/fragment/dialog gets resumed.
If you don't want the changes from the task to persist, then don't store the changes until onPostExecute()
Solution 4:
When Android stops your app because the user hit the back or home button, your dialogs are closed for you. Usually the trick is to preserve the dialogs between onStop()/onStart(). So unless you need to do more than just close the dialog, I'd say don't worry about it.
EDIT: On your activity that hosts the dialog, you may still want to close the dialog if it's still open inside onStop(). This helps prevent memory leaks. But this doesn't need to be triggered from AsyncTask.
Like i said above, the problem is what happens when you hit onStart() again and your AsyncTask is NOT finished yet. You'll need to figure out a way to determine that and re-open that dialog if needed.
Solution 5:
After numerous redesigns I finally settled on an approach that seems to work. However, I haven't been able to find certain aspects of my design documented elsewhere in discussions of Android development. So I'd be interested in any feedback.
In summary, what I had to do is this:
- onSaveInstanceState() - if SomeTask is running, I use a simple lock to block SomeTask from exiting doInBackground() during pause.
- onResume() - I had to put in a switch statement to handle different resume situations. If launching app I do nothing as nothing is paused, if restarting after being hidden or after config change I release the lock so that the preserved SomeTask instance can resume where it left off, etc.
- onDestroy() - I cancel SomeTask if it is running.
I'll put the code fragments for this solution in my original post.
Post a Comment for "How Can You Handle Dismissing A Dialogfragment (compatibility Lib) Upon Completion Of An Asynctask"