Reputation: 75
I have a form screen written in Compose where the user can fill out some TextField
s and submit it. If they press the submit button before filling out the fields, the supporting text for the empty TextField
s will change to say "Required".
To do this, I've created a ViewState
that includes a showRequired
field:
data class ViewState(
showRequired: Boolean = false,
// other fields
)
In my view model, I create a MutableStateFlow
with the ViewState
as its default value:
private val _viewState: MutableStateFlow<ViewState> = MutableStateFlow(ViewState())
val viewState: StateFlow<viewState> = _viewState.asStateFlow()
To have the composable show the required message, I update the view state like this:
fun onSubmitClicked(form: Form) {
if (areRequiredFieldsMissing(form)) {
_viewState.update { it.copy(showRequired = true) }
}
}
In my composable, I'm collecting that flow with collectAsStateWithLifecycle()
and then checking if showRequired = true
before showing the message:
val viewState = viewModel.viewState.collectAsStateWithLifecycle()
if (viewState.value.showRequired) {
// set supportingText = Required
}
When I click the submit button, the message shows as expected. However, if I leave this screen and then come back to it, the required message shows by default instead of being hidden. I thought I could fix this by updating the view state when the back button is pressed to set showRequired = false
:
// in my view model
fun onBackPressed() {
_viewState.update { it.copy(showRequired = false) }
}
However this doesn't fix anything. When I come back to the form screen, the required message still shows by default. When I debug the composable, the value collected for showRequired
is still true. Why doesn't updating it to false work correctly, but updating it to true when clicking the submit button does? I think I'm misunderstanding how StateFlow
works but I'm not sure what to try.
EDIT: I resolved this by resetting my ViewState
when exiting the screen so that showRequired
is reset to false, instead of just updating it.
_viewState.value = ViewState()
Upvotes: 2
Views: 2243
Reputation: 14877
This is my understanding of the use case.
Add a comment if it is not exactly the same.
You have a screen with multiple TextFields
and a Submit Button
. On clicking the submit button, the app navigates to a different screen.
The states are maintained in a shared ViewModel between screens.
The error message is to be shown only after the user clicks on the button, but validation fails.
My first suggestion would be to use screen level ViewModel
instead of shared ViewModel
which automatically clears the states when it is removed.
But, there may be scenarios where that is not possible. In that case, you can try this,
ViewModel
// To store the state of the TextField input
private val _textFieldState: MutableStateFlow<String> = MutableStateFlow("")
val textFieldState: StateFlow<String> = _viewState.asStateFlow()
// To store the state of the CTA button click
private val _ctaClicked: MutableStateFlow<Boolean> = MutableStateFlow(false)
val ctaClicked: StateFlow<Boolean> = _viewState.asStateFlow()
// To store the state of the CTA button click
private val textFieldErrorState: MutableStateFlow<String?> = combine(
textFieldState,
ctaClicked,
) { textFieldState, ctaClicked, ->
if (ctaClicked && textFieldState.isEmpty()) {
"Required"
} else {
null
}
}
We are using combine
from Kotlin to listen to both the TextField
input state as well as the CTA button click state.
You should reset the ctaClicked
when navigating away from the screen.
Comment with more details, if this is not working for your use-case.
Upvotes: 0
Reputation: 91
showRequired
should be a separate SharedFlow shouldn't keep in state as StateFlow, showing a message when there is an empty field on submit is an event not a state, you should show it only once. StateFlow is keeping its value and when you come back from a different screen recomposition occurs and
val viewState = viewModel.viewState.collectAsStateWithLifecycle()
if (viewState.value.showRequired) {
// show message
}
this code block gets executed again, and because showRequired
in state is set true message will be shown.
You should remove showRequired
from your viewState
private val _formWarningEvent = MutableSharedFlow<Boolean>()
val formWarningEvent = _formWarningEvent.asSharedFlow()
in Composable
LaunchedEffect(Unit) {
viewModel.formWarningEvent.onEach { show -> // can register lifecycle or use collect etc.
if (show) // show message
}
}
Upvotes: 0