There is function collectAsState()
applicable to a StateFlow
property in order to observe it in a Composable
.
A composable requires a StateFlow
because StateFlow
guarantees an initial value. A Flow
doesn't come with that guarantee.
Now, what is the way to go if I have a StateFlow
property but I want to apply an operator (like map
) before collecting the Flow
in the Composable
?
Here an example:
Let's say a repository exposes a StateFlow<MyClass>
val myClassStateFlow: StateFlow<MyClass>
data class MyClass(val a: String)
... and a view model has a dependency on the repository and wants to expose only the property a
to its Composable
...
val aFlow = myClassState.Flow.map { it.a } // <- this is of type Flow<String>
The map
operator changes the type from StateFlow<MyClass>
to Flow<String>
.
aFlow
has no initial value anymore? After all its first emission is derived from the initial value of myClassStateFlow
.Flow
back into StateFlow
at some point. Which is the more idiomatic place for this?
stateIn()
? How would the code look like?collectAsState(initial: MyClass)
and come up with an initial value (although myClassStateFlow
had an initial value)?Currently there is no built-in way to transform StateFlow
s, only Flow
s. But you can write your own.
Way I ended up solving was to use the example in that post.
First create a notion of a DerivedStateFlow
.
class DerivedStateFlow<T>(
private val getValue: () -> T,
private val flow: Flow<T>
) : StateFlow<T> {
override val replayCache: List<T>
get () = listOf(value)
override val value: T
get () = getValue()
@InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<T>) {
flow.collect(collector)
}
}
Then have an extension on StateFlow
like the current map
extension on Flow
fun <T1, R> StateFlow<T1>.mapState(transform: (a: T1) -> R): StateFlow<R> {
return DerivedStateFlow(
getValue = { transform(this.value) },
flow = this.map { a -> transform(a) }
)
}
Now in your Repository or ViewModel, you can use it as below.
class MyViewModel( ... ) {
private val originalStateFlow:StateFlow<SomeT> = ...
val someStateFlowtoExposeToCompose =
originalStateFlow
.mapState { item ->
yourTransform(item)
}
}
Now you can consume it as you expect in Compose without any special work, since it returns a StateFlow
.