Lucas Mann
Lucas Mann

Reputation: 467

MutableState in view model?

In all the examples I have seen with view models in combination with Jetpack Compose, one usually stores a state in the view model as MutableStateFlow and then applies collectAsState in the compose function in order to get a Compose state.

My question: Why not store the state directly in the view model, and not some flow? E.g.

class MyViewModel: ViewModel() {
    val showDialog = mutableStateOf(false)
}

@Compose
fun MyScreen(viewModel: MyViewModel) {
    Button(onClick = { viewModel.showDialog = true })
    if (viewModel.showDialog) {
        AlertDialog(...)
    }
}

The above code seems to run as intended. Is this a valid solution then?

Upvotes: 27

Views: 26844

Answers (4)

Chenhe
Chenhe

Reputation: 1033

I think it is better to use MutableStateFlow rather than mutableStateOf of Compose in ViewModel.

  1. ViewModel should be independent of UI. It is not a good choice to use Compose in VM.
  2. MutableStateFlow is multi-thread safe. You can use MutableStateFlow.update{} to update it in any thread or any coroutine context. However, mutableStateOf is not. You should use Snapshot.withMutableSnapshot{} to guarantee atomic updates in a multi-thread environment. See official document.
    PS: viewModelScope is hardcoded to Dispatcher.Main, so in most case it's OK to use mutableStateOf.
  3. It's hard to be activity-level lifecycle aware if you use mutableStateOf directly in ViewModel. There is no direct way to notify the upstream data source to stop updating when the application is switched to the background. For Flow Compose provides a extend function collectAsStateWithLifecycle(), see the blog for details.

Of course, if this is a simple program, I think it is acceptable to use Compose state directly in ViewModel (Some official documentation does the same).

Upvotes: 11

Andy H.
Andy H.

Reputation: 465

Using mutableStateOf() in a view model will work, but this is no longer the best approach.

If you look at the latest version of the official documentation, including the Now in Android reference app, view models should stay data-centric and therefore use the data-centric MutableStateFlow instead of the Compose-centric mutableStateOf().

Android Developer Guide – Foundation: Screen UI state

Android Developer Guide – App Architecture: Mutating the UI state from background threads

Now in Android – View Model

Now in Android - Screen

Using MutableStateFlow also makes much more sense from a unit test perspective. When testing a view model in isolation, any dependency on Compose (or other UI-centric packages and classes) should be a red flag. In other words, why would you need any Compose dependencies when testing a view model?

Upvotes: 16

Cyber Avater
Cyber Avater

Reputation: 2147

Flow comes from kotlinx.coroutines it's used to observe async changes, while State is for updating compose UI changes. You're to use Flow regardless of UI if you're following subscribe to stream for changes pattern. Again, you can follow some complicated/incomplete pattern, but why would you?

Upvotes: -2

Richard Onslow Roper
Richard Onslow Roper

Reputation: 6817

Yes it certainly is. I don't know where you saw those examples, but this is indeed the recommended practice. You can check the State Codelab; it demonstrates how to replace the LiveData objects to mutableStateOf inside the viewmodel. Also, as far as the usage of LiveData and Flow is concerned, it is mainly for interoperability, as far as I know. The apps which are not fully built in Compose, but are being transferred, or apps which plan to use the view system alongside Compose. mutableStateOf is only for Jetpack compose and hence, developers will want to use LiveData in such cases. However, if you are building a brand new project, and want it to be composed of only Compose, then definitely go for what you've mentioned in the question. It is the correct way.

Upvotes: 21

Related Questions