I want commit a fragment after network background operation. I was calling commit() after successful network operation but in case activity goes to pause or stop state it was crashing app saying IllegalState exception.
SO I tried using commitAllowingStateLoss() and its working fine now.
I gone through few blogs and articles it says commitAllowingStateLoss() is not good to use.
Whats the way to handle commit fragment after network operation handling activity pause and stop state?
commit Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
A transaction can only be committed with this method prior to its containing activity saving its state. If the commit is attempted after that point, an exception will be thrown. This is because the state after the commit can be lost if the activity needs to be restored from its state.
commitAllowingStateLoss
Like commit() but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
And According to your question you are using AsyncTask for network background operation. So this might be a problem that you are commiting fragment inside its callback method. To avoid this exception just dont commit inside asynctask callbacks methods. This is not a solution but precaution.
Well I came across the same problem and found a very simple workaround. Since android os doesn't have any solution that time ( Still don't have a good one though
commitAllowingStateLoss()
is one of their solution, and you know the rest).The solution was to write a Handler class that buffers the messages when activity is pass and plays them on
onResume
again.By using this class, Make sure you all the code which
asynchronously
changingfragment
state ( commit etc) is called from a message in this `handler.Extend
FragmenntPauseHandler
from handle class.Whenever your activity receives an
onPause()
callFragmenntPauseHandler.pause()
and foronResume()
callFragmenntPauseHandler.resume()
.Replace your implementation of the Handler
handleMessage()
withprocessMessage()
.Provide a simple implementation of
storeMessage()
which always returns true.Below is a simple example of how the PausedHandler class can be used.
On the click of a
Button
a delayed message is sent to thehandler
.When the
handler
receives the message (on the UI thread) it displays aDialogFragment
.If the
FragmenntPauseHandler
class was not being used anIllegalStateException
would be shown if the home button was pressed after pressing the test button to launch the dialog.I've added a
storeMessage()
method to theFragmenntPauseHandler
class in case any messages should be processed immediately even when the activity is paused. If a message is handled then false should be returned and the message will be discarded.Hope it helps. I used this one in 4 apps and never had the same issue again.I will try to explain to you everything in detail.
FragmentTransaction
in the support library provides four ways to commit a transaction,1) commit()
2) commitAllowingStateLoss()
3) commitNow()
4) commitNowAllowingStateLoss()
You probably are getting an
IllegalStateException
saying that you cannot commit afteronSaveInstanceState()
gets called. Check this post which describes why this exception is thrown in the first place.The
commit()
andcommitAllowingStateLoss()
is almost identical in its implementation with one difference,commit()
checks if the state has already been saved, and if so then it throws anIllegalStateException
. You can check the source code yourself.So, do you lose the state everytime you call
commitAllowingStateLoss()
. No, certainly not. You MAY lose the state of the FragmentManager or any other fragment added or removed after onSaveInstanceState() if the app is killed.Here is a practical scenario for you use case -
onSaveInstanceState()
gets calledcommitAllowingStateLoss()
These are the two possible things that can happen now -
If the system killed your app due to lack of memory, then your app will be recreated again with the saved state made in step 2. FragmentB will not be visible. This is when you lose the state.
If the system did not kill your app and the app is still in memory, then it will be brought back to the foreground and FragmentB will still be displayed. In this case, you have not lost the state.
You can check this Github project and try this scenario yourself.
If you have turned on the “Don’t Keep Activities” developer option, you will experience the first scenario in which the state is truly lost.
If you have that setting off, you will experience the second scenario in which no state is lost.
Just check whether activity is finishing or no, and then
commit()
the transaction.The exception you get is in a scenario, when activity is being finished, and you are trying to commit a new transaction, which obviously won't be saved by
FragmentManager
because theonSavedInstanceState()
has already been executed. That's why framework forces you to implement it "correctly".I'd like to add informations to Aritra Roy (so far i readed, it's a really good answer).
I encountered the problem before, and i found that the main problem is that you are trying to make some async operations (HTTP, computations, ...) in another thread, wich is a good pratice, but you must inform your user AFTER receiving answers.
The main problem is that as it is async operations, there is no guarantee that the user is still on your activity/app anymore. And if he went away, there is no need to do UI changes. Moreover, as android may kill your app/activity for memory issues, you have no guarantees to be able to get your answer, and save it to be restored. The problem is not only "the user can open another app" but "my activity can be recreated from configuration change" and you may be trying to do UI changes during activity recreation which would be really, really bad.
Using "commitAllowingStateLoss" is like saying "i don't care if the UI is not really in the good state". You can do it for little things (like activate a gif saying your download ended)... That's not a big issue, and this problem is not really worth dealing with it as "in general" the user will stay on your app.
But, the user did something, you're trying to get informations on the web, information is ready, and you have to show it when the user resume the app... the main word is "resume".
You must gather the data you needed into a variable (and if you can, a parcelable or primitive variable), and then, override your "onResume" or "onPostResume"(for activities) functions in the following way.
Additional informations : This topic and especially @jed answer, and @pjv, @Sufian comments to it. This blog in order to understand why the bug occurs, and why proposed/accepted answers work.
Last word : Just in case you wonder "why using a service is better than asyncTask". For what i understood, that's not really better. The main difference is that using service properly allow you to register/unregister handlers when your activity is paused/resumed. Therefore, you always get your answers when your activity is active, preventing the bug to occurs.
Notice that's not because the bug does not occurs that you are safe. If you made changes directly on your views, there is no fragmentTransactions involved, therefore, no guarantee that the change will be retained and recreated when the app is recreated, resumed, relaunched, or anything else.
Simple way would be waiting for
Activity
to resume, so you cancommit
your action, a simple workaround would look something like this:So if
Fragment
is resumed (HenceActivity
) you cancommit
your action but if not you will wait forActivity
to start tocommit
your action:P.S: Pay attention to
moveToNext = false
; It is there to ensure that aftercommit
you won't repeatcommit
in case of coming back using back press..