Apologize this is a fairly long post, let me try to explain the background first:
I've read a lot of posts on this topic (and Alex's excellent blog post on this topic), and the general conclusion seems to be not perform fragment transactions in an async callback (see Dianne's post), like AsyncTask#onPostExecute()
.
However I have 2 cases where this is necessary:
An Activity
showing a login Fragment
, when user press the login button, an AsyncTask
starts to authenticate with server, then when login success is returned, the login Fragment
is replaced with the main app Fragment
.
An Activity
showing the main app fragment, when user triggers some action that requires logging in, a login fragment replaces the main fragment which is added to backstack. Again when the login button is pressed, AsyncTask
authenticates with server, then when login succeeds, we want to pop backstack to reveal the main Fragment
to the user and let them do the action they wanted to perform.
Case 1 can be solved by using commitAllowingStateLoss
, but Case 2 is tricky as there is no such flavor of popBackStack in FragmentManager
.
In any case, both of these cases require special handling of app going to background during AsyncTask#doInBackground()
, causing onPostExecute()
getting called when app is in background. One solution is to use Fragment.isResumed to guard replace fragment or pop backstack, and then handle process killed situation by logging in again or save some flag indicating a successful recent login and replace/pop the login fragment on app restore state (login Fragment
is restored to top by FragmentManager
). Or allow state loss, and handle process killed then restored situation, check recent login and remove the login fragment.
Is this how you would handle this? It feels like a lot of work just to handle a very common situation.
Like x90 mentioned in the comment above, the easiest solution would be to split your fragments up into separate activities. This will obviously avoid the exception because launching a new activity doesn't require modifying the state of the previous activity. Swapping fragments in/out of the screen like this is probably not what you want to do anyway... as their names suggest, Fragment
s were designed to serve as "fragments" of the user interface, and not as much as entire screens which make up the user interface. (Of course, I don't mean to suggest that you aren't allowed to use Fragment
s in this way... I'm just saying that it wasn't a design goal of the Fragment
API).
You could also try to implement some sort of cancellation policy in your AsyncTask
. That is, if the activity ever begins to go into the background, cancel the task immediately. When the AsyncTask
finally finishes, make sure that isCancelled()
returns false
before performing fragment transactions, etc.