Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.lang.IllegalStateException: Cancel call cannot happen without a maybeRun

I am trying to test a simple ViewModel with Robolectric.

Here's my ViewModel

GreetingsViewModel.kt

@FlowPreview
@ExperimentalCoroutinesApi
class GreetingsViewModel : ViewModel() {


    private val greetingsChannel = ConflatedBroadcastChannel<String>()

    /**
     * Whenever greetingsChannel offers a value, prepend `Hello ` with it and return.
     */
    val greetingsResponse = greetingsChannel.asFlow()
        .flatMapLatest { name ->
            flowOf("Helo $name")
        }.asLiveData(viewModelScope.coroutineContext)

    /**
     * To get new greetings
     */
    fun askGreetings(name: String) {
        greetingsChannel.offer(name)
    }
}

and here's my unit test file

GreetingsViewModelTest.kt

@FlowPreview
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class GreetingsViewModelTest2 {

    @Test
    fun greeting_askGreetings_getWithName() {
        val testViewModel = GreetingsViewModel()
        testViewModel.askGreetings("john")
        testViewModel.greetingsResponse.getOrAwaitValue().should.equal("Hello john")
    }
}

But, when I run the test am getting below error.

java.lang.IllegalStateException: Cancel call cannot happen without a maybeRun

    at androidx.lifecycle.BlockRunner.cancel(CoroutineLiveData.kt:184)
    at androidx.lifecycle.CoroutineLiveData.onInactive(CoroutineLiveData.kt:245)
    at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:440)
    at androidx.lifecycle.LiveData.observeForever(LiveData.java:232)
    at com.theapache64.twinkill.test.GetOrAwaitValueKt.getOrAwaitValue(getOrAwaitValue.kt:26)
    at com.theapache64.twinkill.test.GetOrAwaitValueKt.getOrAwaitValue$default(getOrAwaitValue.kt:15)
    at com.theapache64.tracktor.ui.activities.test.GreetingsViewModelTest.greeting_askGreetings_getWithName(GreetingsViewModelTest.kt:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:546)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:252)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

Any idea why ?

NOTE: getOrAwaitValue() is an extension function to get the value of a LiveData or waits for it to have one, with a timeout.

like image 404
theapache64 Avatar asked Apr 27 '20 15:04

theapache64


People also ask

How do I resolve Java Lang IllegalStateException?

To avoid the IllegalStateException in Java, it should be ensured that any method in code is not called at an illegal or inappropriate time. Calling the next() method moves the Iterator position to the next element.

What is meant by Java Lang IllegalStateException?

Signals that a method has been invoked at an illegal or inappropriate time. In other words, the Java environment or Java application is not in an appropriate state for the requested operation.

How do I handle IllegalStateException on Android?

Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. You should look into one of your thread and make it synchronized with your UI thread. The way to do this in Android is with Handler.

Is IllegalStateException a checked exception?

IllegalStateException is the child class of RuntimeException and hence it is an unchecked exception.


2 Answers

This is a bug in LiveData library, and it should be fixed in version 2.3.0

You can test this fix in version 2.3.0-alpha05.

More details in the changelog and the issue tracker

https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0-alpha05 https://issuetracker.google.com/issues/157840298

like image 63
ibit Avatar answered Oct 14 '22 01:10

ibit


I was facing the same problem. As @Rahul Singhal was pointing the cause of the error was this line

    [email protected](this)

So I decided to convert the LiveData back to flow and then get the value. This is the extension I'm using for getting the value now.

    suspend fun <T> LiveData<T>.getFlowAsLiveDataValue(timeMillis: Long = 2_000): T? =
        withTimeoutOrNull(timeMillis) {
        [email protected]().first()
    }

Your test then should look like

    testViewModel.greetingsResponse.getFlowAsLiveDataValue().should.equal("Hello john")
like image 38
atorresveiga Avatar answered Oct 14 '22 01:10

atorresveiga