What's the best or recommended way to talk between...
# compose
o
What's the best or recommended way to talk between two viewmodels used in single composable function, any guidance link would be of great value
s
I would say that you don’t, either the composable gets what it needs from both independently, or you merge the two ViewModels into one. You should most definitely keep the ViewModel interaction towards the top level of your composable tree and have a “stateful” composable that wires up “stateless” composables to the ViewModel, those stateless ones don’t have to care about how or where the data comes from, the stateful one is the one that coordinates
t
Copy code
sealed interface Event 
class OnTaskOneFinished(
    valueFromFoo : Int
) : Event

class FooViewModel {
    
    private val events : MutableSharedFlow<Event>
    
    fun performTaskOne(){
        viewModelScope.launch {
            // task goes here
            val myValue = calculateValue()
            events.value = OnTaskOneFinished(myValue)
        }
    }
}

@Composable
fun MyComposable(
    foo : FooViewModel,
    bar : BarViewModel,
){
    LaunchedEffect(foo, bar){
        foo.events.collect { event ->
            when(event){
                is OnTaskOneFinished -> {
                    bar.onTaskOneFinished(event.valueFromFoo)
                }
            }
        }
    }
    
    Button(
        onClick = {
            foo.performTaskOne()
        }
    ) {
        Text("Click Me!")
    }
}
@Sam Pengilly Any problem with this approach ?
o
Would this be a good practice? Any gotchas here?
s
Not off the top of my head. If the ViewModels are seen as essentially compose state holders then that approach would be similar to wiring up the behaviours of two state holders at a common ancestor. I tend to focus my “screen” composable and ViewModel relationships to be 1:1 though to avoid mixing too many separate concepts together, but that’s just personal preference. I’d say that as long as the ViewModels are kept to the top level of this part of your tree it’s fine, it wouldn’t be all that great to have ViewModels being passed around to Composables at lots of different levels (e.g. some child deep within the tree getting its own ViewModel for something). It works for state holders (like
SnackbarHostState
) but less so for ViewModels, I’d say if arbitrary injection into composables/state holders becomes possible then there wouldn’t be a need for using ViewModels in compose at all
d
I would stick to a single ViewModel for a screen. Even reading this:
Copy code
bar.onTaskOneFinished(event.valueFromFoo)
Implies that bar knows something about foo, ie TaskOne. (which sounds like a UseCase object to me) When really you are saying that this screen needs to know about both.
Even the even Flow could be eliminated and replaced by an observable ScreenState object. It could contain a property that communicates something more semantically rich. Your composable here just seems to act like glue between things that could exists together.