StateManagement ‐ Demo - devrath/ComposeAlchemy GitHub Wiki

𝙲𝙾𝙽𝚃𝙴𝙽𝚃𝚂
How to manage the states
How to have the states in view-model
Getting a reference of view-model in compose the vanilla way
Observing the state in the compose UI from view-model
Properly setting the Preview and Route for observing ViewModel state
Properly updating the states from User action

How to manage the states

  • Components
    • UI -----------> Reacts to the state changes.
    • data-calss ---> Holds the state.
    • view-model ---> Manages the state of the UI and stores the state.
  • By doing above, We keep the UI as pure as possible.
  • We can also keep the state at a single place and modify it and alter it and if any bugs 🐞 arise we can identify it easily in one place.

How to have the states in view-model

Method -1

class NumberGuessViewModel : ViewModel() {
    var state by mutableStateOf(NumberGuessState())
        private set
}
  • Please look at keeping the data class in a mutable state.
  • We have only a private setter method.
  • This way we can modify the state only from inside the view model and observe the state from outside the view model.

Method -2

class NumberGuessViewModel : ViewModel() {
    private val _state = MutableStateFlow(NumberGuessState())
    val state = _state.asStateFlow()
}
  • Observe here we have a private value of a mutable state flow that can only be modified from inside the view model.
  • A public flow is exposed that can be observed from outside.

Getting a reference of view-model in compose the vanilla way

View-Model

class NumberGuessViewModel : ViewModel() {
    private val _state = MutableStateFlow(NumberGuessState())
    val state = _state.asStateFlow()
}

UI

@Composable
fun NumberGuessDemoScreen(modifier: Modifier = Modifier) {
    val viewModel = viewModel<NumberGuessViewModel>()
}

Observing the state in the compose UI from view-model

Dependencies

implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
  • We should always use collectAsStateWithLifecycle because the flow is collected with awareness of fragment, and activity lifecycles.
  • Also it makes sure the the flow is not active when the app is not active in the foreground and is in the background.
@Composable
fun NumberGuessDemoScreen(modifier: Modifier = Modifier) {
    val viewModel = viewModel<NumberGuessViewModel>()
    val state by viewModel.state.collectAsStateWithLifecycle()
}

Properly setting the Preview and Route for observing ViewModel state

  • Observe how the NumberGuessDemoRoute is the root element.
  • It hosts the view model reference in one place.
  • This also is a step toward how to keep the UI as dumb as possible
@Composable
fun NumberGuessDemoRoute(navController: NavHostController) {
    val viewModel = viewModel<NumberGuessViewModel>()
    val state by viewModel.state.collectAsStateWithLifecycle()
    NumberGuessDemoScreen(
        data = state,
        onAction = viewModel::onGuessAction,
        modifier = Modifier.fillMaxSize()
    )
}

@Composable
fun NumberGuessDemoScreen(
    modifier: Modifier = Modifier,
    onAction: (NumberGuessAction) -> Unit,
    data: NumberGuessState
) {

}

@Preview(
    showBackground = true,
    backgroundColor = 0xFFF
)
@Composable
private fun CurrentScreenPreview() {
    CodeTheme {
        NumberGuessDemoScreen(
            data = NumberGuessState(),
            onAction = {}
        )
    }
}

Properly updating the states from User action

  • Also observe how instead of passing many actions we can use a sealed class and use it as a common action to delegate all the actions bubbling up to the root parent.
  • Finally we delegate to viewModel::onGuessAction to view-model.

Code-UI

@Composable
fun NumberGuessDemoRoute(navController: NavHostController) {
    val viewModel = viewModel<NumberGuessViewModel>()
    val state by viewModel.state.collectAsStateWithLifecycle()
    NumberGuessDemoScreen(
        data = state,
        onAction = viewModel::onGuessAction, // Forwards all the action from the composable to the view model reference
        modifier = Modifier.fillMaxSize()
    )
}

@Composable
fun NumberGuessDemoScreen(
    modifier: Modifier = Modifier,
    onAction: (NumberGuessAction) -> Unit,
    data: NumberGuessState
) {

    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        TextField(
            value = data.numberText,
            onValueChange = { newText ->
                onAction(NumberGuessAction.OnNumberTextChange(newText))
            }
        )
    }
}

@Preview(
    showBackground = true,
    backgroundColor = 0xFFF
)
@Composable
private fun CurrentScreenPreview() {
    CodeTheme {
        NumberGuessDemoScreen(
            data = NumberGuessState(),
            onAction = {}
        )
    }
}

Code-ViewModel

class NumberGuessViewModel : ViewModel() {

    private val _state = MutableStateFlow(NumberGuessState())
    val state = _state.asStateFlow()

    fun onGuessAction(action: NumberGuessAction){
        when(action){
            is NumberGuessAction.OnGuessClick -> {

            }
            is NumberGuessAction.OnNumberTextChange -> {
                _state.update {
                    it.copy(numberText = action.numberText)
                }
            }
            is NumberGuessAction.OnStartNewGameButtonClick -> {

            }
        }
    }

}

Code-State

sealed interface NumberGuessAction {
    data object OnGuessClick : NumberGuessAction
    data class OnNumberTextChange(val numberText: String) : NumberGuessAction
    data object OnStartNewGameButtonClick: NumberGuessAction
}
⚠️ **GitHub.com Fallback** ⚠️