android 如何防止对案例进行不必要重组?

k4ymrczo  于 2023-06-27  发布在  Android
关注(0)|答案(1)|浏览(127)

我的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中声明它也没有帮助。

2mbi3lxu

2mbi3lxu1#

我认为你可以通过将showSnackbar提取到副作用块中来解决它。
以下是官方文档副作用#launchedefect的示例

LaunchedEffect(uiState.error) {
        showSnackbar(error, SnackbarDuration.Short)
    }

和错误提取逻辑应单独放置到ViewModel中

相关问题