Stephan Schuster
11/03/2025, 3:20 PMonCreate() method where I need to observe and/or init state.
After some investigations and tests I came up with this pattern which I used for quite a while now:
class MyViewModel(
private val repository: MyRepository
) : ContainerHost<MyState, Nothing>, ViewModel() {
override val container = container<MyState, Nothing>(MyState()) {
intent {
repeatOnSubscription {
coroutineScope {
launch { observeX() }
launch { observeY() }
launch { observeZ() }
}
}
}
intent {
initState()
}
}
private suspend fun observeX() = subIntent {
repository.myFlow.map { it.toWhatever() }.collect {
reduce { state.copy(whatever = it) }
}
}
...
private suspend fun initState() = subIntent {
repository.someOneShotSuspendFunction() // or reduce initial state
}
...
fun doSomething() = intent {
...
}
}
• Question 1: Does this pattern make sense as a universal pattern?
◦ Probably it can be simplified for certain scenarios, but as said, I was looking for something that is universally applicable.
◦ If interested, please check my personal notes below on this topic.
NOTES:
container:
- this "onCreate" method is actually an Orbit intent from which we spawn 2 new intents
- 2 root intents because repeatOnSubscription blocks and initState would never be called
- we pack initState into a separate intent for consistency
- and because we want observers to be registered before initState (not sure if that matters)
- we use rOs because without it all observers would keep running in background without ui
- we create coroutineScope directly under rOs
- this gets linked to the single ref-count coroutine that is used by rOs
- if rOs cancels, this scope gets cancelled and hence all our observe#()
- without the coroutineScope under rOs it does not work, observe#() keep running when no UI
- all observe#() methods must be put into launch{}, otherwise first would block others
- when removing coroutineScope and last launch {}, rOs works for the last observeZ() method
- TL;DR: I assume rOs will only cancel children directly attached to it (or its scope/coroutine)
- in other words, without coroutineScope all launch {} get attached to a scope which is not rOs (?)
- we package all observe#() methods and also initState() for consistency into a subIntent
- subintent are lighter than intents and basically suspend functions with access to Orbit syntax
- see docs on subIntent which lists multiple flow collection in container as primary target
things which do NOT work (as expected/desired):
A: initState never called
override val container = container<MyState, Nothing>(MyState()) {
repeatOnSubscription {
coroutineScope {
launch { observeX() }
launch { observeY() }
launch { observeZ() }
}
}
intent {
initState()
}
}
B: observers keep running in the background when no ui
override val container = container<MyState, Nothing>(MyState()) {
intent {
coroutineScope {
launch { observeX() }
launch { observeY() }
launch { observeZ() }
}
}
intent {
initState()
}
}
C: observers keep running in the background when no ui --> rOs does not cancel observe#()
override val container = container<MyState, Nothing>(MyState()) {
intent {
coroutineScope {
repeatOnSubscription {
launch { observeX() }
launch { observeY() }
launch { observeZ() }
}
}
}
intent {
initState()
}
}
D: observeX() blocks, Y and Z are never executed/collected
override val container = container<MyState, Nothing>(MyState()) {
intent {
repeatOnSubscription {
observeX()
observeY()
observeZ()
}
}
intent {
initState()
}
}
E: all observers collect, rOs works for observeZ() but X and Y keep running
override val container = container<MyState, Nothing>(MyState()) {
intent {
repeatOnSubscription {
launch { observeX() }
launch { observeY() }
observeZ()
}
}
intent {
initState()
}
}
So far, this pattern worked well under all circumstances. Until now. And this brings me here because I think I might have misunderstood things.
In my current VMs initState() I use a HiveMQ MQTT client to publish a message. If my device has network turned on, everything works as expected. But if network is turned off, that initState() never returns and it seems that this blocks the Orbit event queue and consequently any `fun doSomething() = intent { ... }-`methods on my VM are never executed.
• Question 2: Could someone explain me this behavior?
◦ How could something in initState() block the surrounding intent {}, the onCreate-intent and the event loop?
◦ Is the onCreate-intent and intents launched therein executed as part of the Orbit event queue?
◦ My understanding was that an intent {} block (also inside onCreate) simply adds a task to the event queue.
◦ But if so, why don't I see a log statement which I put after intent { initState() } <log statement here never shows up if no network>?