I’m a little puzzled - I try to build a base thing...
# coroutines
d
I’m a little puzzled - I try to build a base thing that offers a
val flow: StateFlow<T>
to consumers, while individual implementations can fill in the initial state via some
protected abstract suspend fun initialState(): T
. But I can’t get lazy initialization working, i.e. my backing
MutableSharedFlow<T>(replay = 1)
needs suspending to be converted into a
StateFlow
or needs the initial value being given again (repeated), which defeats the purpose, i.e.
val flow: StateFlow<T> get() = backingFlow.onStart { emit(initialState()) }.stateIn(someScope)
does not work, because
stateIn(someScope)
is suspending. What am I doing wrong?
s
What would you expect the StateFlow’s
value
property to return before the initial state has been computed?
c
If you’re exposing a StateFlow, you should manage the flow internally as a StateFlow as well, not a SharedFlow. The semantic meaning of the StateFlow gets a little messy if it doesn’t always have a value available, and in fact the contract of a StateFlow is that it must always have a value. The alternative is lazily creating the StateFlow itself, but that doesn’t fix the problem, because you cannot subscribe to an “uninitialized” StateFlow and be notified when that initial value is ready. A StateFlow by design is meant to convey a value at a point in time. A value that has not been loaded yet is not simply missing from time and space, it must be represented by something that that communicates the known absence of the value. What you’s need to do is require consumers to provide an initial state and then lazily update that state with the computed value, or else return a
StateFlow<T?>
and set the initial value to null until the lazy value has been computed.
d
Yeah, I see, the dreaded
.value
property needs to be accessible all the time. Guess I have to rethink my architecture a little then. Will probably start with a real static / empty value and only afterwards fill in realistic things.
Thanks for your time!