This documentation about using ViewModel with Compose states:
Note: Due to their lifecycle and scoping, you should access and call ViewModel instances at screen-level composables, that is, close to a root composable called from an activity, fragment, or destination of a Navigation graph. You should never pass down ViewModel instances to other composables, pass only the data they need and functions that perform the required logic as parameters.
However, a ViewModel is scoped to the Activity or Fragment, so I would expect it to be OK if you call viewModel() wherever you like in the hierarchy, and even in multiple places in the same hierarchy. I would expect to be getting the same instance returned regardless.
viewModel() deeper in the hierarchy than the root composable of a screen?The obvious drawback to following this advice is that you may have to do a lot of state hoisting, leading to Composables with many parameters that must be connected manually and correctly, making code less readable and maintainable.
It is actually completely fine to use viewModel() anywhere any deep in your hierarchy. There is no performance impact whatsoever since viewmodels are cached by design and are retrieved instantly with that call. Then what could go wrong? It just goes against some of the essential coding principals that would make your code a nightmare to read.
@Composable
fun UserProfileCard() {
val viewModel = viewModel<UserViewModel>()
Column {
UserAvatar() // Calls viewModel() internally
UserName() // Calls viewModel() internally
UserBio() // Calls viewModel() internally
UserStats() // Calls viewModel() internally
EditButton() // Calls viewModel() internally
}
}
The above composable looks concise and right to the point. No bunch of parameters to pass down. viewModel() is supposed to save us both the effort and eye strain of passing down so many parameters. Right? Congratulations! You just:
@Preview
@Composable
fun UserAvatarPreview() {
UserAvatar() // CRASH - No ViewModelStoreOwner in preview mode
}
@Test
fun testUserAvatar() {
composeTestRule.setContent {
UserAvatar() // Need to mock ViewModelProvider, ViewModelStore,
// ViewModelStoreOwner, set up Hilt/Koin/whatever,
// create the actual ViewModel with all dependencies...
}
}
Someone reading UserAvatar() has literally no idea it depends on a ViewModel. They have to:
Open the file
Read the implementation
Find the viewModel() call
Figure out what UserViewModel does
Trace where that ViewModel comes from
Pray it's scoped correctly
Meanwhile, this is as clear as day and immediately readable and obvious (makes it a lot more maintainable?):
@Composable
fun UserAvatar(avatarUrl: String) {
Image(avatarUrl)
}
If you're passing down too many parameters, then you're doing something wrong. Maybe break down your composables to smaller pieces. Someone else might work on your code and get himself in a quicksand of code mess that they can't get out off. Always prioritize maintainability and readability, it's not always about performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With