I faced the following problem:
There's the registration screen, which has several input fields on it. When a user enters something, the value is passed to ViewModel, set to screen state and passed back to the screen via StateFlow. From the composable, I'm observing this StateFlow. The problem is that Composable is not invalidated after emitting the new value to the Flow.
Here's the ViewModel code:
class RegistrationViewModel : BaseViewModel() {
private val screenData = CreateAccountRegistrationScreenData.init()
private val _screenDataFlow = MutableStateFlow(screenData)
internal val screenDataFlow = _screenDataFlow.asStateFlow()
internal fun updateFirstName(name: String) {
screenData.firstName = name
updateScreenData()
}
private fun updateScreenData() {
viewModelScope.launch {
_screenDataFlow.emit(screenData.copy())
}
println()
}
}
Here's the composable code:
@Composable
fun RegistrationScreen(navController: NavController, stepName: String) {
val focusManager = LocalFocusManager.current
val viewModel: RegistrationViewModel by rememberInstance()
val screenData by viewModel.screenDataFlow.collectAsState()
Scaffold {
ConstraintLayout(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(onTap = {
focusManager.clearFocus()
})
},
) {
val (
textTopLabel,
textBottomLabel,
tfFirstName,
tfLastName,
tfEmail,
tfPassword,
btnNext
) = createRefs()
...
RegistrationOutlinedTextField(
value = screenData.firstName ?: "",
constraintAsModifier = {
constrainAs(tfFirstName) {
top.linkTo(textBottomLabel.bottom, margin = Padding30)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
},
label = { Text("First Name", style = PoppinsNormalStyle14) },
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_user_login),
contentDescription = null
)
},
onValueChange = { viewModel.updateFirstName(it) }
)
}
}
Thanks in advance for any help
CodePudding user response:
Your problem is that you're mutating your state, so the equals used in the flow always returns true.
Change this
internal fun updateFirstName(name: String) {
screenData.firstName = name
updateScreenData()
}
private fun updateScreenData() {
viewModelScope.launch {
_screenDataFlow.emit(screenData.copy())
}
println()
}
to
internal fun updateFirstName(name: String) {
viewModelScope.launch {
_screenDataFlow.emit(screenData.copy(firstName = name))
}
}
CodePudding user response:
In your case your MutableStateFlow
holds a link to a mutable value, that's why when you pass the value which hashcode
(which is calculated on all field values) is identical, the flow doesn't update the value.
Check out Why is immutability important in functional programming?
data class
is a nice tool to be used, which will provide you all copy
out of the box, but you should emit using var
and only use val
for your fields to avoid mistakes.
Also, with MutableStateFlow
you always have access to value
, so you don't need to store it in a separate variable:
class RegistrationViewModel : BaseViewModel() {
private val _screenDataFlow = MutableStateFlow(CreateAccountRegistrationScreenData())
internal val screenDataFlow = _screenDataFlow.asStateFlow()
internal fun updateFirstName(name: String) {
_screenDataFlow.update { screenData ->
screenData.copy(firstName = name)
}
}
}