Handling IllegalStateException: Can not perform this action after onSaveInstanceState

Handling IllegalStateException: Can not perform this action after onSaveInstanceState

Image for post

Have you ever came across this error? If you have not seen it yourselves, you might have seen it in your crash analysis report e.g. Fabric Crashlytic.

Image for post

Most people would just google and found the Stackoverflow answer

IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager

I’m getting user reports from my app in the market, delivering the following exception?

stackoverflow.com

And simply change

transaction.commit();

to

transaction.commitAllowingStateLoss();

That?s all okay now, no more crash! But?

A better way of solving

As usual, the procedure to solve a problem is 1. Replicating It, 2. Understanding It, 3. Fixing It.

Replicate it

The most difficult part of this problem is, replicating it. One of the cause many people resort to using commitAllowingStateLoss is, this problem is hard to replicate.

I have created a way to replicate this consistently in one of my demo project.

elye/demo_android_handling_commit_after_onsavedstateinstances_error

demo_android_handling_commit_after_onsavedstateinstances_error – Showing a way to replicate IllegalStateException: Can?

github.com

After compile, and just perform the steps as per the video below. You?ll see it occurs consistently. Note: If the error doesn?t happen, you have to ensure you don?t have Don’t Keep Activity setting ON..

In short, every time you change your Location Setting externally, it will crash.

Understanding it

You could try debugging it yourselves to see what cause the problem? as long as don?t use commitAllowingStateLoss() to solve it ?

If you are lazy to try it out yourselves, let me help you here. Add the below code to it.

private fun openFragment(tag: String, instantiateFragment: () -> Fragment) { if (supportFragmentManager.findFragmentByTag(tag) == null) { Log.d(“Tag”, “Fragment Transaction Commit”) supportFragmentManager.beginTransaction() .replace(R.id.container, instantiateFragment(), tag) .commit() }}override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) Log.d(“Tag”, “onSaveInstanceState Called”)}

After that follow the step again to replicate the problem (you could uninstall and reinstall to follow the exact step). You?ll see the log as below.

07?08 19:00:36.847 D/Tag: Fragment Transaction Commit07?08 19:00:38.382 D/Tag: onSaveInstanceState Called07?08 19:00:39.242 D/Tag: Fragment Transaction Commit

The main cost of the problem is the fragment transaction is commit() after onSaveInstanceState() is called.

This is something undesirable, as that means your final state is not saved before you change Fragment. (but it is okay if you don?t care? that?s why Google provides commitAllowingStateLoss())

So in short, if you ever see this happens on your end, it?s likely that some of your fragment transaction is called after onSaveInstanceState(). You?ll need to then debug your flow see how that could occurs.

Fixing it

Understanding how this happens, we?ll look at the flow of our program.

When the program first start, it will call the below twice

locationManager.locationDetection { onResume() }

due to the checkPermissionAndRequest() functionality.

When user click OPEN LOCATION Page, it will perform the below

  • The onPause which trigger locationManager.removeSwitchStateReceiver()
  • The onSaveInstanceState() as it is existing the App.

Because it has called twice locationManager.locatioDetection which register the location listener twice, but only call the removeSwitchStateReceiver once, hence one of the registered location change listener is not unregister upon exiting the App.

When the user change the Location Setting, it will then trigger the call of onResume() again, even after onSaveInstanceState() is called.

Hence this will trigger the Fragment commit(), which cause the crash

Image for post

To fix this problem properly, we?ll just need to remove the Location Change Listener when a new Location Listener is setup, as per the bold code below.

fun locationDetection(onDetectChange: () -> Unit) { removeSwitchStateReceiver() gpsSwitchStateReceiver = object : BroadcastReceiver() { // .. other codes } activity.registerReceiver(gpsSwitchStateReceiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)) }}

You could get the fixed code in

Uncommon notes on Enabling Location in Apps

Apps accessing to location is relative common these days. But there are many considerations in doing so, and not as?

medium.com

TL;DR;

Don?t worry about how this problem is fixed. My message here is simple, don?t just use

commitAllowingStateLoss();

Instead try to locate the actual problem, and solve it. Only if time pressure and you are certain that the view shown is not important to have the state saved prior to the transaction, then well? business priority comes first.

Thanks for reading. You can check out my other topics here.

You can follow me on Medium, Twitter, Facebook, and Reddit for little tips and learning on mobile development, medium writing, etc related topics. ~Elye~

25