StateFlow with final state

I have a little bit philosophical question. I’m using StateFlow for returning the processing state of my API, it does exactly what I want. To provide the latest state at any time, even in the time when processing is already done. I just don’t like one thing.

When I use this:
progressFlow.collectLatest { ... }

It is never finished, so the parent’s job is also never finished. I expect that this is an issue also from a resources point of view, right?

So I always must somehow handle the final state like by takeWhile(), which then skips the last element or us this

progressFlow.transformWhile { state ->
        emit(state)
        state !is ScannerState.FinishStatus
    }.collect { state -> ... }

I don’t like the fact that all users of this API (this StateFlow) need to care about it and need to repeat this check.
Is there any better way?

I can use normal flow and cancel it. But I will lose the possibility to get the last value even when processing is done and also I will lose the diret access value.

So I would like to keep all benefits of StateFlow, but I just want to tell as API author, that there will be no more updates and all consumers can stop waiting on it, but all of them still can get the latest final state.

So, I also wanted takeWhile to emit the last value so I made an extension function takeUntilwith the code you described passing the condition as a function.

Anyway, if you want consumers to not worry about doing such a check, only let them consume the resulting flow. That is, make your MutableStateFlow private, and expose the result of your tansformWhile as a Flow via something like val exposedFlow: Flow<Type> = mutableFlow.transformWhile{...}

1 Like

Yes. I was thinking about returning the resulting flow in a such way. Maybe it is a good idea. It will just lose the possibility to call quick access to the current value that StateFlow has.

If you are only interested in the final value, you can call last on the flow to get the last value from the flow. If you are interested in every value, collect can take a lambda that is invoked for every value emitted to the flow.

You only loose access to .value externally, internally you’ll still have access to it. AFAIK there isn’t really a good reason to have that exposed externally anyway (because as explained above you can get each value as it is emitted anyway)

Since StateFlows are hot, by definition they never complete. The collector would need to cancel the job of the coroutine used to collect it.

val job = scope.launch {
    progressFlow.collectLatest { state ->
        ...
        if (state is ScannerState.FinishStatus) {
            job.cancel()
        }
    }
}
1 Like