Hi everyone! I recently came across a case where *...
# compose-android
d
Hi everyone! I recently came across a case where
@Stable
was being used on a ViewModel. My understanding is that since ViewModels are inherently "unstable," the developer used
@Stable
to prevent unnecessary recompositions.
Copy code
@Stable
data class MyScreenState(val age: Int)

@Stable
@HiltViewModel
class MyViewModel @Inject constructor(): ViewModel() {
    private val _uiState = MutableStateFlow(MyScreenState())
    val uiState: StateFlow<MyScreenState> = _uiState.asStateFlow()
}

@Composable
fun MyScreen(
   viewModel: MyViewModel() = viewModel()
) {
   val uiState by viewModel.uiState.colletAsStateWithLifecycle()
}
However, I’m questioning whether it’s actually necessary to use
@Stable
on a ViewModel. While I understand the intent, I’m not convinced it’s essential. For instance, if I structure the code as shown below,
Copy code
@Composable
fun MyScreenRoot(
    viewModel: MyViewModel() = viewModel()
) {
    val uiState by viewModel.uiState.colletAsStateWithLifecycle()

    MyScreen(uiState)
}

@Composable
fun MyScreen(
    uiState: MyUiState
) {
    // do something here
}
I can still use Previews, and I don't anticipate significant recompositions occurring simply because the ViewModel itself is considered unstable. I’m curious to hear your thoughts on this.
e
We're using a very similar approach, and we don’t care that viewmodels aren’t stable, the root composable should not recompose anyway. Do you have any performance issues? I wouldn’t optimize whatever isn’t broken
d
@Ernestas That is exactly my point. If you separate the components into a ScreenRoot and a Screen, those issues simply don't occur. Like you, I don't worry about the "unstable" nature of the ViewModel, nor do I go out of my way to force it to be stable. Furthermore, I have never actually encountered any performance issues with this approach. I’m just curious if there is some hidden catch or a specific edge case that I might be missing.
a
When strong skipping mode is enabled (which is the default behavior), even if the view model is unstable, the composable won't recompose if the view model is the same instance (which is always the case), so marking the view model stable doesn't bring any benefits but can potentially cause bugs.
d
@Albert Chang Yeah, the Strong Skipping Mode is also enabled now. That's a great point. Thank you!
p
Despite strong skipping. Marking a non-stable type as Stable is wrong and nobody should do that. Is not a workaround for anything nor a trick for some benefit, is just wrong. The ViewModel should be only referenced by the root Composable, be it a screen or container component. Said that, the reference won't change, so no recomposition will trigger. And when the reference changes you better want your root Composable to recompose. This is one of the reasons the ViewModel should not be passed down as a prop in the Composable tree.
1