Search code examples
androidandroid-jetpack-composekotlin-stateflow

Flow doesn't update Composable


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


Solution

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