It seems like there is no way to know when a `bind...
# mvikotlin
s
It seems like there is no way to know when a
binding
has started collecting. I have code along the lines of:
Copy code
binder = bind {
    renderer.events.map { it.toIntent() } bindTo store
}.apply { start() }
But I have no way to know when it is safe to call
renderer.dispatch
because the call to
start()
is asynchronous, and non-blocking. It would be nice to provide an API similar to
stateIn
, which offers a default implementation that launches a job to start sharing, and a secondary suspending
stateIn()
function that blocks until the sharing has begun. In MVIKotlin we could offer suspending
start()
that would block until the flows are all actively collecting. Thoughts? Am I missing something?
PS, I realize I can do
bind(Dispatchers.Main.immediate)
, but that won’t work on all platforms.
This is a terrible hack:
Copy code
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
private val isReady: MutableStateFlow<Boolean> = MutableStateFlow(false)

binder = bind {
    renderer.events.map { it.toIntent() } bindTo store
    flowOf(true) bindTo ::handleReady
}.apply { start() }

fun handleReady(isReady: Boolean) {
  scope.launch { isReady.emit(isReady) }
}

fun dispatch(event: Event) {
  scope.launch {
     isReady.first { it }
     renderer.dispatch(event)
  }
}
a
What is
renderer
?
s
Ah, sorry. It is an
BaseMviView
a
Is it SharedFlow?
s
a
Ah got it, thanks.
BaseMviView
doesn't have any magic, it's
events
subscription fully synchronous. The sync behaviour comes from the binder, which launches a coroutine. I think that having
suspend fun start
wouldn't help, because you would still have to launch a coroutine. So I think there are multiple possible solutions. 1. Actually use
Dispatchers.main.immediate
as you suggested. It should be supported on Android, JavaFx, Swing and Apple (Darwin) platforms. 2. Try creating your own
BaseMviView
backed by
MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
3. Don't use binder and just collect manually.
Notice that the Rx-based
Binder
is not affected by this issue, because it always subscribes synchronously.
s
Yeah, I agree that those (plus my silly hack) seem like the options. I think it would be nice if the framework provided a way to do (2), though. WDYT?
a
Could you elaborate, a way of what exactly? That's the fundamental issue with coroutines, subscriptions are asynchronous unless you use the immediate dispatcher. Even MutableSharedFlow with infinite extra buffer capacity would solve the issue only for the first subscriber. Subsequent subscribers may still miss emissions.
So just using the immediate dispatcher is sufficient.
s
I meant provide a way to tell
BaseMviView
to use a
MutableSharedFlow
with infinite buffer. I’m targeting JS as well as JVM and Darwin, and so
immediate
is not an option for me. I agree that you’d need to be a bit careful with the shared flow approach, but it does solve the issue, and it provides the same behavior as the Rx implementation.
a
but it does solve the issue
As I mentioned, it solves the issue only for the first subscriber. Subsequent subscribers may still miss emissions.
and it provides the same behavior as the Rx implementation
Nope. Rx doesn't require any buffering, because it always subscribes synchronously. In fact, buffering may add undesired behaviour, e.g. outdated events are still buffered.
I will think if we could add something to MVIKotlin.
Btw, it looks like that immediate dispatcher is also supported on JS. Could you please check?
s
🤦‍♂️ I’m not sure why I thought it wasn’t supported on JS!? https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-coroutine-dispatcher/immediate.html It clearly says
Common
. homer disappear
I tested it, and it does work on JS.
🎉 1
a
Yeah, the doc looks outdated. It says "Dispatchers.Main supports immediate execution for Android, JavaFx and Swing platforms". I will report it to JetBrains.
👍 1