Wafi_ck
Wafi_ck

Reputation: 1378

Collect state from StateFlow

I develop the app in Compose and MVVM architecture. I have viewModel with state of view per screen. ViewModel:

class ProfileViewModel : ViewModel() {

    private val _state = MutableStateFlow(ProfileViewState())
    val state: StateFlow<ProfileViewState> get() = _state

    val actions = Channel<ProfileAction>()

    init {
        viewModelScope.launch {
            combine(_state) {
                ProfileViewState()
            }.collect {
                _state.value = it
            }
    }
}

State:

class ProfileViewState {
    val nick: MutableStateFlow<String> = MutableStateFlow("default nick")
}

When user press the button I change value of "nick" in state.

_state.value.nick.value = "new nick"

I want to observe the state in my view and update data automatically when they changed

@Composable
fun ProfileScreen() {
        val viewModel: ProfileViewModel = viewModel()
        val viewState by viewModel.state.collectAsState()
}

TextView where I try to display "nick" value

Text(text = viewState.nick.value, style = MaterialTheme.typography.h4)

The problem is that when I change data in state, screen doesn't react and doesn't update. When I open ProfileScreen I get the same instance of state(?) with default values

Upvotes: 2

Views: 4272

Answers (2)

Gabriele Mariotti
Gabriele Mariotti

Reputation: 364858

With simple case you can use mutableStateOf("default nick") in the ProfileViewState instead of MutableStateFlow.

Something like:

class ProfileViewState {
    var nick  by mutableStateOf("default nick")
}

and in your composable:

  Text(text = viewState.value.nick, style = MaterialTheme.typography.h4)
  Button(onClick = { viewState.value.nick= "new nick"}){Text("Update")}

If you want to use the MutableStateFlow you have to observe the nick.
Something like:

val nick = viewModel.state.value.nick.collectAsState()

Text(text = nick.value, style = MaterialTheme.typography.h4)
Button(onClick = { viewModel.state.value.nick.value  = "new nick"}){Text("Update")}

In this case you can also provide a function in the viewModel to update the nickname:

class HelloViewModel : ViewModel() {
    //...
   
    fun onNameChange(newName: String) {
        _name.value = newName
    }
}

class ProfileViewState {

    val nick: MutableStateFlow<String> = MutableStateFlow("default nick")
   
    fun onNickChange(newNick: String) {
        nick.value = newNick
    }
}

and then in your composable just use:

Button(
     onClick = { viewModel.onNickChange("new nick2")}
){ /*..*/ }

Upvotes: 4

R&#243;bert Nagy
R&#243;bert Nagy

Reputation: 7690

This is probably because you are using nested StateFlows and collecting only the outer one, while updating the inner one. I suggest moving to the following implementation:

data class ProfileViewState(val nick: String, ...)

// Update it like:
fun updateNick(nick: String){
     _state.value = _state.value.copy(nick = nick)
}

This way viewModel.state.collectAsState() should work

Upvotes: 1

Related Questions