Search code examples
androidillegalstateexceptiononsaveinstancestate

IlegalStateException when onItemClick after restart


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?


Solution

  • 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