To reproduce, get the SSCCE Android Project on Github and :
Touch the hamburger to display navigation menu
Select Employees
Select an Employee
Touch the back button
Touch the Overview button
Select the application from the list
Touch the hamburger to display navigation menu
Select Employees
Select an Employee => IllegalStateException
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1538) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1556) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:696) at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:662) at example.com.replacefragments_onitemclick.fragments.FragmentChange.onFragmentChange(FragmentChange.java:88)
fragmentTransaction.commit(); // IllegalStateException: FragmentChange.java:88
The reason for the exception is clear: With the replace statement, it is trying to replace a fragment that is attached to a now non-existent Activity instance.
Overriding onSaveInstanceState()
as suggested here has no effect.
Numerous questions suggest using commitAllowingStateLoss()
. It does not fix the problem and apparently is kind of a hack anyway.
Also, there are answers that say to keep a reference to the old Activity. This does not seem right.
How can I prevent this exception?
The general problem is losing the context. You are right that keeping a reference to the old Activity is a bad way because the Android system should manage the Activity's lifecycle. But you kept the reference to FragmentManager
in FragmentChange
class when static FragmentChange
instance is created (FragmentChange.java : 46)
.
So what really happens: you created static instance of FragmentChange
. The FragmentChange
instance keeps reference to FragmentManager
that is linked with MainActivity
instance . When you press back, the system calls the MainActivity
instance lifecycle callback. It calls onStop()
callback that also invokes dispatchStop()
in FragmentManager
instance where to mStateSaved
variable is assigned to true
. And after that the MainActivity
instance is destroyed. But FragmentChange
instance is kept because the application isn't destroyed, just the activity instance . When you return to the app, the new MainActivity
instance will be created. But your FragmentChange
still keeps the reference to the old FragmentManager
instance that is linked to the dead MainActivity
instance . So no one will call dispatchCreate()
, or dispatchResume()
, or any other method to restore the mStateSaved
value to false
, the old FragmentManager
instance isn't linked to the app's life cycle. And when you select Employees once again, the old FragmentManager
instance throws IllegalStateException
because mStateSaved
still has true value.
The general solution is not to create the static reference to Activity, Fragment, View, FragmentManager and so on. In general, to all classes which lifecycle is Android system business. The one of the possible solutions for your case is not to keep a reference to the FragmentManager
but to send it as a parameter to onFragmentChange. I provided a code example in the pull request https://github.com/emnrd-ito/ReplaceFragment-OnItemClick/pull/1/files