swalkner
swalkner

Reputation: 17339

Android: How to unit test "IllegalStateException: Can not perform this action after onSaveInstanceState"

I'm having a crash in my app where sometimes a dialog.show is called after the activity's lifetime. I know where this happens and would like to unit test every bug that occurred in the app to avoid it to appear again. But how can something like this be (unit?) tested?

Upvotes: 3

Views: 499

Answers (1)

dawid gdanski
dawid gdanski

Reputation: 2452

It's difficult to unit test the exception because the occurrence is tightly bound to the Activity lifecycle as the exception message suggests - the isolation of the occurrence is practically impossible .

You could employ Robolectric and try to verify whether the dialog.show() method is invoked before onSaveInstanceState call but I would not approach the problem this way. And the tests using Robolectric are no longer unit tests.

I met with a few solutions that eliminated the exception occurrence:

  1. You could instantiated an internal queue storing functions deferring the FragmentTransaction-related methods execution and recognize whether the activity has called onSaveInstanceState at the time the show() method is attempted to be executed. If the activity is in the created/started/resumed state you could execute show() immediately. If not, store the function deferring the show() execution and execute them

A few lines of pseudocode below:

   if (isCreatedOrStartedOrResumed) {
      dialog.show()
   } else {
      internalQueue.add {
         dialog.show()
      }
   }

Has the activity returned to the resumed state, execute all pending functions

fun onResume() {
   super.onResume()
   while(internalQueue.isNotEmpty()) {
      internalQueue.poll().invoke()
   }
}

This approach is not immune to configuration change though, we lose the deferred invocations once the activity gets rotated.

  1. Alternatively, you could use ViewModel which is designed to retain the activity state across configuration change such as the rotation and store the deferred executions queue from the 1st approach inside the view model. Make sure the functions storing deferred dialog.show() executions are not anonymous classes - you may end up with memory leak introduced.

Testing:

  1. The way I would test the dialog displaying gracefully would be the Espresso instrumentation tests.

  2. I would also unit test view model storing/executing deferred executions. If we consider structuring code using MVP or MVVM architectural pattern we could store the deferred executions queue within one of the architecture class members and unit test them too.

  3. I would also include LeakCanary as a safety net against memory leaks.

  4. Optionally, we could still use robolectric and develop integration test verifying:

    • the internal queue deferring dialog.show() executions after onSaveInstanceState gets called
    • the internal queue executing pending dialog.show() executions stored after activity has called onSaveInstanceState and returned to the resumed state again.
    • the dialog.show() executed immediately in case the activity is in created/started/resumed state.

That's all I have at the moment, hope you will figure out the satisfactory tests suite verifying correct dialog displaying based on the approaches suggested.

Upvotes: 3

Related Questions