I hava a question about compose state handling, in...
# compose-android
y
I hava a question about compose state handling, in my case i have code string in my uiState, if my code length is 6, i have to call check() which in the viewmodel, now i am listening my ui state in the LaunchEffect, what is reccomended way to do it, where should i listen my code string in viewmodel or in my screen?
Copy code
@Composable
fun CreatePasscodeScreen(viewModel: CreatePasscodeVM = hiltViewModel()) {

    val uiState = viewModel.uiState.collectAsStateWithLifecycle()

    LaunchedEffect(uiState.value.enteredPasscode) {
        if (uiState.value.enteredPasscode.length == 4) {
            viewModel.check()
        }
    }

    Scaffold(
        modifier = Modifier.fillMaxSize(),
        snackbarHost = {
            if (uiState.value.errorMessage.isNotEmpty()) {
                Box(
                    modifier = Modifier
                        .padding(horizontal = 16.dp)
                        .clip(RoundedCornerShape(8.dp))
                        .fillMaxWidth()
                ) {
                    Text(
                        uiState.value.errorMessage,
                        style = DavrTheme.typography.h1Medium,
                        fontSize = 16.sp
                    )
                }
            }
        }
    ) {
        PasscodeContent(
            modifier = Modifier.padding(it),
            titleResId = uiState.value.titleResId,
            numberClick = viewModel::numberClick,
            fingerClick = viewModel::fingerClick,
            delete = viewModel::delete,
            forgotClick = viewModel::forgotClick,
            error = uiState.value.error,
            enteredPasscode = uiState.value.enteredPasscode
        )
    }
}
this is my way but some of my colleagues think that we should do that in viewmodel, if i do it, i may break UDF(unidirectional data flow) pattern
d
If the viewmodel already knows/holds the string and knows when it changes, why can't it call check "internally" ?
s
If you want ViewModel to handle that without needing to do something in UI, you could use
onEach {}
operator if you are using StateFlow APIs.
Copy code
// ViewModel
val state: StateFlow = uiState.onEach {
    if (state.length == 4) {
      // TODO: Your logic
  }
}
This block would run on every flow emission.
y
Copy code
private val _uiState = MutableStateFlow(PasscodeUiState())
val uiState get() = _uiState.asStateFlow()

init {
    viewModelScope.launch {
        _uiState.onEach {
            if (it.enteredPasscode.length == 4) {
                check()
            }
        }
    }
}
I don't know why my code is not working both uiState and _uiState
s
onEach { }
is not a terminal operator. You need to call
collect()
with it as well.
Copy code
_uiState.onEach {
            if (it.enteredPasscode.length == 4) {
                check()
            }
        }.collect {}
a
better would be to directly use the
collect
instead of using
onEach
as intermediate operator
Copy code
_uiState.collect {
    if (it.enteredPasscode.length == 4) {
        check()
    }
}