我的NavHost中包含以下登录屏幕代码:
@Composable
fun LoginScreen(
loginViewModel: LoginViewModel = koinViewModel(),
navController: NavController,
showSnackbar: (String, SnackbarDuration) -> Unit
) {
Timber.i("LoginScreen recomposed")
val uiState by loginViewModel.uiState.collectAsState()
val ssnInput = loginViewModel.ssnInput
ScreenContent(
isLoading = uiState.isLoading,
userInput = ssnInput.takeIf { it.length != SSN_LENGTH } ?: stringResource(id = R.string.saved_ssn, ssnInput.substring(0, 8)),
onInputValueChanged = { loginViewModel.updateSsnUserInput(it) },
onLogInClick = { loginViewModel.sendBankIdRequest() },
isSsnInvalid = uiState.error is InputError,
onKeyboardDone = { },
onClearClick = { loginViewModel.clearInputText() }
)
uiState.error?.let {
val messageId = when (it) {
InputError -> R.string.invalid_personal_number_format
BankIdFailed -> R.string.bankid_failed_check_ssn
NetworkError -> R.string.bankid_internal_error
GenericError -> R.string.bankid_failed
NoGMSError -> R.string.no_gms_error
}
showSnackbar(stringResource(id = messageId), SnackbarDuration.Short)
}
}
在ScreenContent内部,我有一个Box,其中包含一个用作屏幕背景的Image,以及一个Column,其中包含组成屏幕其余部分的组件(标题文本,自定义文本字段,登录按钮,阅读更多可点击文本和徽标图像)。我创建了ScreenContent,这样我就可以在预览中使用它,因为它不支持ViewModels。
我遇到的问题是,每当我点击登录按钮,这反过来又调用loginViewModel.sendBankIdRequest()
,uiState.error得到更新InputError作为值(这是我想要的)和LoginScreen得到重组的结果,这导致showSnackbar被调用,这是预期的行为时,点击按钮。然而,从这一点开始,每次调用onInputValueChanged
lambda时,LoginScreen都会重新组合,每当我在文本字段中输入或删除一个字符时都会发生这种情况。这会导致每次由于重组而调用showSnackbar,即使uiState没有更改。
我该怎么解决这个问题?
我可以通过更新uiState并在每次更新文本输入值时将error设置为null来缓解这种情况,但我不认为这是一种解决方案。我想要的解决方案是除非uiState被更改,否则LoginScreen不会被重新组合。将userInput
参数的值更改为loginViewModel.ssnInput
而不在ssnInput中声明它也没有帮助。
1条答案
按热度按时间2mbi3lxu1#
我认为你可以通过将showSnackbar提取到副作用块中来解决它。
以下是官方文档副作用#launchedefect的示例
和错误提取逻辑应单独放置到ViewModel中