Hi! Say that on a given component, I have a child ...
# decompose
r
Hi! Say that on a given component, I have a child slot. I want to expose a single
uiState
flow to my UI. As part of that UI State, if child slot is active, I want to provide the child instance, if it is not active I want to provide some other state data class. The way I see it, to do this, I would combine the
Value<ChildSlot<X, Y>>
with some other internal state flow. Question is: can I convert the
Value
into a StateFlow to allow me to combine it? Or is there any consequence of doing so, such as performance considerations or the like? I have a snippet that I think came from you @Arkadii Ivanov to do that conversion, I can paste on the thread.
Snippet I got somewhere, can't remember exactly where 😄
Copy code
fun <T : Any> Value<T>.toStateFlow(): StateFlow<T> = ValueStateFlow(this)

@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
private class ValueStateFlow<out T : Any>(private val source: Value<T>) : StateFlow<T> {

    override val value: T
        get() = source.value

    override val replayCache: List<T>
        get() = listOf(source.value)

    override suspend fun collect(collector: FlowCollector<T>): Nothing {
        val flow = MutableStateFlow(source.value)
        val disposable = source.subscribe { flow.value = it }

        try {
            flow.collect(collector)
        } finally {
            disposable.cancel()
        }
    }
}
(Also as a side note, notice the
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
-- should we come up with a another way that doesn't envolve inheritance? 🤔)
a
Yeah, I posted that snippet here a while ago. I think it should be fine to use
@ExperimentalForInheritanceCoroutinesApi
in an end project. However, it might become an issue if used in a library, due to possible binary incompatible changes. Another option could be:
Copy code
fun <T : Any> Value<T>.asStateFlow(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.Eagerly,
): StateFlow<T> =
    callbackFlow {
        val cancellation = subscribe { trySend(it) }
        awaitClose { cancellation.cancel() }
    }.stateIn(scope = scope, started = started, initialValue = value)
👍 1
r
Thanks for quick reply @Arkadii Ivanov And in regards to the general question of exposing a
StateFlow<Instance>
instead of a
Value<Configuration, Instance>
to UI. Is there any concern there? I don't see why there would be, but just wanted to confirm it 🤔
a
Well, actually there is a concern. By default, if you collect your
StateFlow
via
StateFlow#collectAsState()
, it will use
Dispatchers.Main
. So the UI will always lag one frame behind due to the re-dispatching behaviour of the
Main
dispatcher. This has been actually the cause of various issues with
TextField
getting out of sync with the state. So I suggest to always do something like:
collectAsState(Dispatchers.Main.immediate)
.
👍 2
r
hmm I see yes
ok, I believe I can work with it. Thank you once again @Arkadii Ivanov 🙌
👍 1