Colton Idle
08/24/2023, 3:30 PMJeff Lockhart
08/24/2023, 3:41 PMMR3Y
08/24/2023, 3:42 PMColton Idle
08/24/2023, 3:46 PMColton Idle
08/24/2023, 3:49 PMColton Idle
08/24/2023, 3:53 PMMR3Y
08/24/2023, 11:08 PMInteresting. This stateFlow I'm consuming didn't come from me, so I'm a bit lost on what coroutineScope I'd pass in as well as what SharingStarted to use... but ima look into it.That depends on your use-case, how long should the flow stay in memory? For example, if you have a stateflow in an annotated @HiltViewModel
ViewModel
that holds the state of the current screen displayed, then you would use viewModelScope
as a CoroutineScope and SharingStarted.Lazily
to stop sharing when the scope is cancelled (e.g when the destination is popped off from back stack)MR3Y
08/24/2023, 11:10 PMi wonder if the extension method at the bottom of this would workHaven't tried it honestly, but I think it should work. You can also take a look at the other solutions mentioned in the issue I linked above
bezrukov
08/25/2023, 8:12 AMfun <T, R> StateFlow<T>.mapAsState(mapper: (T) -> R): StateFlow<R> = object : StateFlow<R> {
override val replayCache: List<R>
get() = this@mapAsState.replayCache.map(mapper)
override val value: R
get() = mapper(this@mapAsState.value)
override suspend fun collect(collector: FlowCollector<R>): Nothing {
map(mapper).distinctUntilChanged().collect(collector)
error("StateFlow.collect returned normally")
}
}
it doesn't require any coroutine scope as it won't cause an extra collection but will rely on either stateflow's scope (to get value/replayCache) or the collector scope (when calling collect)
So outside it works as simple as it could be:
val stateFlow: StateFlow<Int> = MutableStateFlow(0)
val mapped: StateFlow<Int> = statFlow.mapAsState { it * 2 }
Colton Idle
08/25/2023, 11:19 AMColton Idle
08/25/2023, 11:20 AMMR3Y
08/25/2023, 11:27 AMColton Idle
08/25/2023, 12:46 PMCLOVIS
08/25/2023, 3:30 PMhow do i just reuse the scope of the initial StateFlow though?You can use any scope, so yes, you can if you want.
the stateflow that is initially created doesn't have to attach itself to a scopeA StateFlow by itself doesn't do any operation, it just stores a value. It's a kind of observable variable, it doesn't do anything, and thus needs no scope. When using
map
, you are creating a pair of the original StateFlow + the operation to run to create new values. That operation is suspend
, and thus needs a scope: stateIn
is not a scope for the created StateFlow, it's a scope for all operations before it.CLOVIS
08/25/2023, 3:32 PMmap
into a StateFlow. Any reason in particular? It should be rarely needed.Colton Idle
08/25/2023, 5:23 PMname: String
All is good in the world.
In another gradle module (that I'm now working on) I need a StateFlow of name
. So originally I was passing StateFlow<Person> but since person is defined in another module I had to add that module as a dep to this module. My team lead yelled at me. so she told me to instead of passing the full heavy type of Person, to just map the name and pass a StateFlow of String/name.
Hopefully that makes sense.
tldr. I have StateFlow<Person> that is created/managed by someone else. I need a StateFlow<String> (Person.name) and so I feel "wrong" assigning it a scope etc. I already have a stateflow. I just wanna make it so I don't need my other module to depend on a new type.Jeff Lockhart
08/25/2023, 5:28 PMstateIn
function. If you're doing this in a viewmodel, it makes sense to use the viewmodel's scope either way.CLOVIS
08/25/2023, 7:09 PMStateFlow.map(): StateFlow
variant that doesn't suspend, since indeed it's safe to create a stateflow that wayCLOVIS
08/25/2023, 7:10 PMColton Idle
08/25/2023, 7:14 PMfun <T, M> StateFlow<T>.map(
coroutineScope : CoroutineScope,
mapper : (value : T) -> M
) : StateFlow<M> = map { mapper(it) }.stateIn(
coroutineScope,
SharingStarted.Eagerly,
mapper(value)
)
?CLOVIS
08/27/2023, 12:16 PMprivate class MappedStateFlow<T, U>(
private val input: StateFlow<T>,
private val transform: (T) -> U,
) : StateFlow<U>, SharedFlow<U> by input.map(transform) {
override val value get() = transform(input.value)
}
However, the StateFlow interface is not stable for inheritance, so this wouldn't be safe in your own code. It would only be safe if added to KotlinX.Coroutines directly.