I have this function `suspend fun foo(progressLamb...
# coroutines
m
I have this function
suspend fun foo(progressLambda: (Long) -> Unit)
and I want to convert this to a function returning a
StateFlow<State>
where
State
is a sealed class providing
Progress
,
Success
etc. Initially I thought of using
callbackFlow
but that seems to be more for functions that run asynchronously. Is there a more appropriate way?
g
Looks that you need standard flow { } builder
m
But I cannot
emit
from within the lambda because it is not a suspend lambda.
Another option (if not using
callbackFlow
) would be something like:
Copy code
val progressFlow = MutableStateFlow(State.Progress(0L))
return flow {
    emit(null)
    foo { progressValue ->
        progressFlow.value = State.Progress(progressValue)
    }
    emit(State.Success)
}.combine(progressFlow) { invokeState, progressState ->
    invokeState ?: progressState
}
Alternatively, without using `combine`:
Copy code
flow {
    val progressFlow = MutableStateFlow(State.Progress(0L)
    coroutineScope { 
        val progressJob = launch { 
            progressFlow.collect { progress -> 
                emit(progress)
            }
        }
        foo { progressValue ->
            progressFlow.value = State.Progress(progressValue)
        }
        progressJob.cancel()
        emit(State.Success)
    }
}
I think I prefer the
combine
approach, because I am more confident there will never be a progress state emitted after the success state.
g
if you wrap some callback, indeed you need callbackFlow
m
Are you saying the other two solutions above, don’t work?
g
no they will work
but looks overcomplicated for me
callbackFlow is probably way to go
m
Copy code
callbackFlow {
    send(State.Progress(0L))
    try {
        foo { progressValue ->
            offer(State.Progress(progressValue))
        }
        send(State.Success)
        close()
    } catch (e: Exception) {
        close(e)
    }
}.conflate() // we only care about the latest value
Or, if there is a Failure state:
Copy code
callbackFlow {
    send(State.Progress(0L))
    try {
        foo { progressValue ->
            offer(State.Progress(progressValue))
        }
        send(State.Success)
    } catch (e: Exception) {
        send(State.Failure(e.message))
    }
    // we need to close() or awaitClose(), but nothing to wait for
    close()
}.conflate() // we only care about the latest value
But is there any guarantee that the progress state cannot be emitted after the Success/Failure state?
g
there is no such guarantee, after all it purely related on foo implementation. if it stop send progress before return. then you are fine,. but if you not sure, implementation is not under your control, you always can just use something like AtomicBoolean as flag and prevent send progress before sending Success
I’m a bit worry about this implementation though, it looks that
foo
is blocking function, so you probably want also add .flowOn(IO/Computation)
m
foo
does not launch another coroutine so I think it’s impossible for the lambda to be called/completed after
foo
returns. So
offer
can only be called before
foo
returns, and so the question comes down to: if
offer
is called before
send
(potentially using a different dispatcher) is the order of emitted values guaranteed to be the same?
g
yes, it guaranteed
👍 1
also you always can use sendBlocking
👍 1
this function anyway blocking
also allows you to avoid missing progress (thought offer looks fine for progress)
m
That’s what I like about the
combine
solution. You get a clear priority of the ‘invoke’ flow over the ‘progress’ flow.
invokeState ?: progressState
Copy code
val progressFlow = MutableStateFlow(State.Progress(0L))
val invokeFlow = flow {
    emit(null)
    try {
        foo { progressValue ->
            progressFlow.value = State.Progress(progressValue)
        }
        emit(State.Success)
    } catch (e: Exception) {
        emit(State.Failure(e.message))
    }
}
return invokeFlow.combine(progressFlow) { invokeState, progressState ->
    invokeState ?: progressState
}
g
I don’t know callbackFlow looks fine too, also has priority
m
Another benefit would be that
StateFlow
will swallow duplicates. Also using separate progress flow, allows us to do things like debounce the progress.
g
also true