Home > Net >  Flow doesn't update Composable
Flow doesn't update Composable

Time:11-29

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)
        }
    }
}
  • Related