Hi, I am trying to convert the generic type of a MutableStateFlow and expose it as a StateFlow using the map operator.
Unfortunately, map can only return a Flow<R>:

I also imagine that adding as StateFlow<Group after the map isn't really a good idea.
So, is there any solution for this case right now, or can we have a map that returns StateFlow?
This has been discussed in #2008. Looks like the current way to deal with this is by using stateIn after map.
What if for instance we need to expose the StateFlow from a Repository pattern component and consume it upper in the hierarchy, in a ViewModel?
We can't hold a CoroutineScope in the Repository to pass it to the stateIn operator, as the scope belongs to the ViewModel instead... is my point of view wrong?
@andreimesina you can use a scope that matches the lifecycle of your repositories. In my case, the repositories are singletons, scoped to the whole application, and thus I use ProcessLifecycleOwner.get().lifecycleScope as the CoroutineScope for shareIn and stateIn.
Alternatively you can just construct your own CoroutineScope objects and use them. However, you might miss Lifecycle callbacks in your Repositories, either for canceling the scope, such as you would in onDestroy or onCleared, or even pausing/resuming the coroutines when your app is in background/foreground, such as onStart and onStop. Using scopes from Lifecycles (LifecycleCoroutineScope) handle that automatically.
If ProcessLifecycleOwner does not fit your repositories for some reason (e.g. they are not scoped to the whole app), you can still have a LifecycleCoroutineScope if you are able to make your repositories implement the LifecycleOwner interface, return a LifecycleRegistry in the getLifecycle() override, and somehow trigger the Lifecycle callbacks at the right moments (e.g trigger onStart when repo is in use and onStop when it isn't).
(Every Lifecycle object, such as LifecycleRegistry has a LifecycleScope)
My personal suggestion would be to build your architecture around the scopes automatically provided by LifecycleOwner components as it makes managing scopes easier.
@andreimesina by the way, why not just using asStateFlow()?
@psteiger asStateFlow() is an extension on MutableStateFlow, whereas map and other operators return a Flow.
So we cannot do
val mutableStateFlow = ...
val stateFlow = mutableStateFlow.map {
...
}.asStateFlow()
Because there is no asStateFlow() for a simple Flow.
It is indeed not there because it has a lot of open design questions.
E.g. who should invoke the map operator, each collector or the upstream? Should it depend on whether the upstream state flow is a standalone object (just created via StateFlow(...) and emitted via flow.value = 42) or shared via stateIn?
What if mapping lambda suspends in the middle, how should the system behave?
All these questions have controversial answers that conflict with each other.
As the potential solution, I'd recommend either using stateIn operator or basic delegation mechanism:
class MappedStateFlow<T, R>(private val source: StateFlow<T>, private val mapper: (T) -> R) : StateFlow<R> {
override val value: R
get() = mapper(source.value)
override val replayCache: List<R>
get() = source.replayCache.map(mapper)
override suspend fun collect(collector: FlowCollector<R>) {
source.collect(object : FlowCollector<T> {
override suspend fun emit(value: T) {
collector.emit(mapper(value))
}
})
}
}
@qwwdfsad Basic delegation is not very good solution since StateFlow is not stable for inheritance.
Most helpful comment
@qwwdfsad Basic delegation is not very good solution since
StateFlowis not stable for inheritance.