kotlinkotlin-coroutineskotlin-stateflow

Kotlin - why does my test on StateFlow return "stops by timeout"?


I am trying to better understand how StateFlow can be used by writing simple code in a scratch file in Android Studio. I just want to update a StateFlow and collect the result. The code is the following:

data class Foo(val id: String, val age: Int)

class Emitter{
    private val _fooState = MutableStateFlow<Foo>(Foo("foo", 0))
    val fooState: StateFlow<Foo> = _fooState.asStateFlow()

    suspend fun emitter(){
        delay(1000)
        _fooState.update {  f ->
            f.copy(
                id = "ooo",
                age = 1
            )
        }
        delay(1000)
        _fooState.update {  f ->
            f.copy(
                id = "eee",
                age = 10
            )
        }
    }
}

fun main() {
    runBlocking {
        val emitter = Emitter()

        println("Start")
        launch{emitter.emitter()}
        println("Done")

        emitter.fooState.collect {
            println(it)
        }
    }
}

main()

The code stops after 30s with the following log: "Couldn't get scratch execution result - stopped by timeout (30,000 ms)". Why is that?


Solution

  • The script waits for your code to finish, but it never does - therefore killing it after the timeout. The reason is that a StateFlow never completes and therefore collecting it will suspend forever. From the documentation:

    State flow never completes. A call to Flow.collect on a state flow never completes normally.

    If you want to retrieve the value the StateFlow currently has, you can simply access its value property (it doesn't even need a coroutine):

    println(emitter.fooState.value)
    

    However, if you want to observe the flow for changes, you need to collect it as you do. But there is no defined end of a StateFlow, so collect will wait indefinitely for new values, even when in your example there won't be any.

    There easily could be more values, though, just imagine another thread also calling emitter.emitter() at some indeterminate time in the future, then there will be two more values which your collector will receive.

    StateFlow is a specially configured SharedFlow, which itself is a variant from the regular Flow. The SharedFlow (and therefore also the StateFlow) is called hot because it produces values, even if there is no collector observing the flow. It runs on its own. A regular flow is called cold because it only runs when a collector is executing it. Where a hot flow never completes, cold flows usually do. For example:

    val flow: Flow<Int> = flow {
        repeat(10) {
            delay(1000)
            emit(it)
        }
    }
    

    As you can see, when the 10 values were emitted there is no way an eleventh value could be emitted, so the flow can safely be shut down and the collector therefore finishes after the last value and continues with the remaining code.

    Please also keep in mind that cold flows can only have a single collector. An additional collector would begin the flow from the start, so technically there would be two different flows, one for each collector.

    That's the main difference to the hot flow which can have any number of collectors, sharing the emitted values. But neither the flow nor the collector can ever know if there won't be any more values (as seen in your example), so they will never complete, suspending their collectors indefinitely.

    Here is another in-depth answer I wrote, mainly to explain the difference between Channel and SharedFlow, but in the second half I also explain in more detail the difference between cold and hot flows: https://stackoverflow.com/a/78448546