Home > Blockchain >  StateFlow is updated but not collected
StateFlow is updated but not collected

Time:12-16

I'm currently learning about the new Android stack (MVVM, compose, kotlin Flow/StateFlow), and I'm having trouble debugging a StateFlow where the value is updated, but I have no sign of collection from the composable.

It's a generic question, but I didn't find any solution to my problem by searching on my own.

Does anybody have an idea about what could disturb a StateFlow? I'm letting my code below:

ViewModel:

@HiltViewModel
class AuthViewModel @Inject constructor(
    private val navigationManager: NavigationManager,
    private val interactor: AuthInteractor
): BaseViewModel() {

    companion object {
        val TAG: String = AuthViewModel::class.java.simpleName
    }

    private val _uiState = MutableStateFlow(AuthenticationState())
    val uiState: StateFlow<AuthenticationState> = _uiState

    fun handleEvent(event: AuthenticationEvent) {
        Log.v(TAG, "new event: $event")
        when (event) {
            is AuthenticationEvent.GoToRegistering -> navigateToRegistering()
            is AuthenticationEvent.Register -> registerAccount(event)
            is AuthenticationEvent.SnackbarMessage -> showSnackBar(event.message, event.type)
        }
    }

    private fun navigateToRegistering() {
        navigationManager.navigate(NavigationDirections.Authentication.registering)
    }

    private fun registerAccount(event: AuthenticationEvent.Register) {
        Log.v(TAG, "register account")
        _uiState.value.build {
            isLoading = true
        }

        viewModelScope.launch {
            Log.v(TAG, "launching request")
            val exceptionMessage = interactor.registerUser(event.login, event.password)
            Log.v(TAG, "response received, launching state from viewmodel")

            _uiState.value.build {
                exceptionMessage?.let {
                    Log.v(TAG, "Exception is not null")
                    isFailure = true
                    snackbarMessage = interactor.selectRegisterError(it)
                }
            }
        }
    }

}

UI Component:

@Composable
fun Authentication(
    modifier: Modifier = Modifier,
    viewModel: AuthViewModel,
    type: AuthenticationType
) {
    val state by viewModel.uiState.collectAsState()
    Log.v("Authentication", "new state: $state, type = $type")

    BackHandler(enabled = true) {
        viewModel.handleEvent(AuthenticationEvent.Back)
    }

    state.snackbarMessage?.let { resourceId ->
        val message = stringResource(resourceId)
        Log.e("Authentication", "error: $message")
        viewModel.handleEvent(AuthenticationEvent.SnackbarMessage(message, SnackbarType.ERROR))
    }

    if (state.isAuthenticated) {
        // TODO: launch main screen
    }

    LaunchedEffect(key1 = state.isRegistered) {
        // TODO: launch login event
    }

    AuthenticationContent(
        modifier = modifier,
        type = type,
        viewModel = viewModel,
        state = state
    )
}

State Data Class:

data class AuthenticationState(
    val isLoading: Boolean = false,
    val isFailure: Boolean = false,
    val isRegistered: Boolean = false,
    val isAuthenticated: Boolean = false,
    @StringRes val snackbarMessage: Int? = null
)  {

    fun build(block: Builder.() -> Unit) = Builder(this).apply(block).build()

    class Builder(uiModel: AuthenticationState) {
        var isLoading = uiModel.isLoading
        var isFailure = uiModel.isFailure
        var isRegistered = uiModel.isRegistered
        var isAuthenticated = uiModel.isAuthenticated
        var snackbarMessage = uiModel.snackbarMessage

        fun build(): AuthenticationState {
            return AuthenticationState(
                isLoading,
                isFailure,
                isRegistered,
                isAuthenticated,
                snackbarMessage
            )
        }
    }
}

CodePudding user response:

You are doing the updates like this:

_uiState.value.build {
    isLoading = true
}

This code takes current value of uiState and calls build function on it. Build function creates new instance of AuthenticationState, but you don't do anything with that new instance and it's discarded. You have to set this new instance to be the new value of your StateFlow, like this:

_uiState.value = _uiState.value.build {}
// or this:
_uiState.update { current -> current.build {} }
  • Related