Got a question regarding collecting a flow inside ...
# flow
s
Got a question regarding collecting a flow inside the scope of mapping some other flow, and I am a bit confused if I could improve the way I am currently approaching this. I have a use case where: I have a stateFlow that holds a Result type of an UUID. With that UUID I am starting a new subscription (a flow) that also returns a result type. Finally with this latest result, if it’s successful, I need to perform another API call to get the final data which I am interested combining the original stateflow data with And all this should at the end maps to the UI state of type ViewState More in thread: 🧵
Initially I was doing a
map
on the originalStateFlow but realized I wouldn’t be able to start a new flow inside it and emit those values whenever they come in, map needed a value directly or at least from a suspending function. I found out
transformLatest
exists and it was the only thing that I managed to do to get it compiling properly.
Copy code
val viewState = originalStateFlow.transformLatest { uuidResult: Result<UUID> ->
    when(uuidResult) {
        Error -> emit(ViewState.Error)
        Success -> {
            val uuid = uuidResult.value
            startSomeOtherSubscription(uuid).collect { subscriptionResult -> // [1] See below for a question
                when(subscriptionResult) {
                    Error -> emit(ViewState.Content(uuid = uuid, extraData = null))
                    Success -> {
                        val extraData = extraDataSupendingApiCall(subscriptionResult.value)
                        emit(ViewState.Content(uuid = uuid, extraData = extraData))
                    }
                }
            }
        }
    }
}.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = ViewState.Loading,
)
Now I wonder: 1. This
collect
inside the flow transformation feels a bit awkward no? Will it work just fine since we’re doing
transformLatest
anyway, meaning that it would be cancelled in case we get a new uuidResult? If I just did
transform
without
latest
would it keep this flow alive forever (or maybe until viewModelScope is cancelled since it’s launched there with
StateIn
) and be a problem? 2. Is doing such a whole new collect flow inside this transform context something that is safe to do? 3. Is there a better way to model this entirely? It feels a bit awkward the way that I’ve done it, and I am not sure it’s because I am not used to this, the requirement of what I am doing is just weird itself or if I am just plain out doing something wrong/suboptimal.
n
1. Not awkward. Yes, you need the "Latest" part or
startSomeOtherSubscription(uuid).collect { ... }
will block new UUIDs from being processed. 2. Yes, it is safe. It's a normal part of creating operators that combine other flows. 3. It's awkward because you have a lot of nesting. Easiest way to fix that is the break parts out into separate helper methods. Here's an example where I replaced the
collect
with
map
and then
emitAll
the result. It's not any safer or more optimal, but sometimes
emitAll
can read better, IMO.
Copy code
val viewState = originalStateFlow.transformLatest { uuidResult: Result<UUID> ->
    when(uuidResult) {
        Error -> emit(ViewState.Error)
        Success -> emitAll(startSomeOtherSubscriptionHelper(uuidResult.value))
    }
}.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = ViewState.Loading,
)

private fun startSomeOtherSubscriptionHelper(uuid: UUID) = startSomeOtherSubscription(uuid)
    .map { subscriptionResult ->
        when(subscriptionResult) {
            Error -> ViewState.Content(uuid = uuid, extraData = null)
            Success -> ViewState.Content(
                uuid = uuid,
                extraData = extraDataSupendingApiCall(subscriptionResult.value)
            )
        }
    }
}
(just typed out in slack so please forgive any errors but I think the intent is clear)
The only issue I see is that when you get a new UUID, you probably want to update your state to Loading.
Just guessing, maybe you want the old data there until you get the new data.
s
Wow thank you so much Nick for taking the time to respond to this. All your points make a lot of sense yeah. And in this case, you are right that I would want the old data to show anyway, so showing Loading again wouldn’t even be a problem. I think my mental block is that I don’t really think about extracting flow operators in separate functions, but I should treat them like any other code 😅