Use case: Adapting non-coroutine APIs that expose both a stream of values and a "current value" property to be StateFlows.
This adapter could take a few different shapes – a top level function, or an extension on a non-StateFlow, it doesn't really matter to me as long as there's a way to do this that doesn't involve manually implementing the StateFlow interface in third-party code.
Non-coroutines code:
interface Preference<T> {
T get()
Observable<T> asObservable()
}
Proposed adapter (rough API):
fun <T> StateFlow(
currentValueProvider: () -> T,
values: Flow<T>
): StateFlow<T> = object : StateFlow<T> {
override val value: T get() = currentValueProvider()
override suspend fun collect(collector: FlowCollector<T>) {
// Necessary? See open questions below.
collector.emit(currentValueProvider())
values.collect(collector)
}
}
Use:
fun <T> Preference<T>.asStateFlow(): StateFlow<T> =
StateFlow(::get, asObservable().asFlow())
The proposed StateFlow constructor's implementation does not really work, as sharing requires its own scope in which the upstream values are being collected.
Here is an easier approach that converts this kind of Prereference instance into a StateFlow using only existing sharing operators as explained in #2047:
fun <T> Preference<T>.stateIn(scope: CoroutineScope): StateFlow<T> =
asObservable().asFlow().stateIn(scope, initialValue = get())
It seems quite compact to me, without any need to introduce additional APIs.
Sorry, I should have been more clear in the original post. In this example/use case, the Preference type is effectively a MutableStateFlow. It already maintains its own notion of "current value", and also already correctly shares asObservable() (in this case using the Rx sharing operators, although that's an implementation detail). Much like MutableStateFlow does not need a scope to operate, neither does Preference. The goal for the constructor I'm proposing is simply to provide a way to expose other types that already satisfy the StateFlow contract as StateFlows.
That said, using the stateIn operator does technically solve this case, although it makes it more complicated than it needs to be. I proposed this constructor function because the scope is not actually necessary.
Right now, I'm manually implementing StateFlow in a way similar to what I posted, but that makes me nervous since Flow.collect is InternalCoroutinesApi and StateFlow is documented as stable for use but unstable for implementing.
It's also reasonable to determine that this is just going to not be supported, if it's just not common enough. I think there are a few types that could benefit from such an adapter however (e.g. RxJava's BehaviorRelay/BehaviorSubject, ReactiveSwift's MutableProperty, Combine's CurrentValueSubject, JavaFx's ObjectProperty to name a few).
@zach-klippenstein Providing this kind of converted would effectively open a window to 3rd party StateFlow implementations. However, the state flow's implementation contract with respect to how its various properties and signals it emits are synchronized with each other is quite complex and not easy to implement correctly. I do not believe that there are any types in the wild "that already satisfy StateFlow contract".
So, StateFlow is restricted to be implemented only inside kotlinx.coroutines on purpose to avoid broken implementations of its many contracts in the wild and so that other operators, encountering a StateFlow, can fully rely on its following the StateFlow contracts.
stateIn is already pretty efficiently implemented. You can just use it when it's out. If you'll have any performance concerns with respect to stateIn and have _specific_ popular 3rd party types in mind, then we can provide well-tested converters to StateFlow from those types in the library.
Most helpful comment
@zach-klippenstein Providing this kind of converted would effectively open a window to 3rd party
StateFlowimplementations. However, the state flow's implementation contract with respect to how its various properties and signals it emits are synchronized with each other is quite complex and not easy to implement correctly. I do not believe that there are any types in the wild "that already satisfy StateFlow contract".So,
StateFlowis restricted to be implemented only insidekotlinx.coroutineson purpose to avoid broken implementations of its many contracts in the wild and so that other operators, encountering aStateFlow, can fully rely on its following theStateFlowcontracts.stateInis already pretty efficiently implemented. You can just use it when it's out. If you'll have any performance concerns with respect tostateInand have _specific_ popular 3rd party types in mind, then we can provide well-tested converters toStateFlowfrom those types in the library.