StateManagement ‐ Demo - devrath/ComposeAlchemy GitHub Wiki
- 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.
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.
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.
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>()
}
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 offragment
, andactivity
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()
}
- 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 = {}
)
}
}
- 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
}