更改 TextField 值时避免重新组合其他元素

问题描述 投票:0回答:1

我的应用程序中有基本的登录屏幕,如下所示:

登录屏幕.kt:

@Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel) {
    val userEmail = viewModel.userEmail.collectAsState()
    val userPassword = viewModel.userPassword.collectAsState()
    val isLoginPending = viewModel.isLoginPending.collectAsState()
    val isLoginButtonEnabled = viewModel.isLoginButtonEnabled.collectAsState()

    val scaffoldState = rememberScaffoldState()

    if (viewModel.getOnboardingStatus() == OnboardingStatus.NOT_COMPLETED) {
        navController.navigate(Screen.Onboarding.route)
    }

    LaunchedEffect(key1 = true) {
        viewModel.loginError.collectLatest {
            it?.let {
                scaffoldState.snackbarHostState.showSnackbar(message = it)
            }
        }
    }

    Scaffold(
        modifier = Modifier
            .fillMaxSize()
            .recomposeHighlighter(),
        scaffoldState = scaffoldState
    ) {
        Box(modifier = Modifier
            .fillMaxSize()
            .recomposeHighlighter()) {
            if (isLoginPending.value) {
                ProgressBar(
                    modifier = Modifier
                        .align(Alignment.Center)
                        .recomposeHighlighter(),
                )
            }
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .align(Alignment.Center)
                    .recomposeHighlighter(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "Login",
                    fontWeight = FontWeight.Bold,
                    fontSize = 36.sp
                )
                Spacer(modifier = Modifier
                    .height(64.dp)
                    .recomposeHighlighter())
                UserInputTextField(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 16.dp)
                        .recomposeHighlighter(),
                    label = "Email",
                    inputState = userEmail.value,
                    onValueChange = { viewModel.onEvent(LoginEvent.EnteredEmail(it)) },
                    isLoginPending = isLoginPending.value
                )
                Spacer(modifier = Modifier
                    .height(8.dp)
                    .recomposeHighlighter())
                UserInputTextField(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 16.dp)
                        .recomposeHighlighter(),
                    label = "Password",
                    inputState = userPassword.value,
                    onValueChange = { viewModel.onEvent(LoginEvent.EnteredPassword(it)) },
                    isLoginPending = isLoginPending.value,
                    keyboard = KeyboardOptions(keyboardType = KeyboardType.Password),
                    visualTransformation = PasswordVisualTransformation()
                )
                Spacer(modifier = Modifier
                    .height(32.dp)
                    .recomposeHighlighter())
                Button(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(
                            start = 16.dp,
                            end = 16.dp
                        )
                        .recomposeHighlighter(),
                    enabled = isLoginButtonEnabled.value,
                    onClick = {
                        viewModel.onEvent(LoginEvent.Login)
                    }
                ) {
                    Text(text = "Login")
                }
            }
            ClickableButton(
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .padding(bottom = 32.dp)
                    .recomposeHighlighter(),
                text = "No account? Click to register",
                onTextClicked = { navController.navigate(Screen.Register.route) }
            )
        }
    }
}

@Composable
private fun UserInputTextField(
    modifier: Modifier,
    label: String,
    inputState: String,
    onValueChange: (String) -> Unit,
    isLoginPending: Boolean,
    keyboard: KeyboardOptions? = null,
    visualTransformation: VisualTransformation? = null
) {
    OutlinedTextField(
        value = inputState,
        onValueChange = { onValueChange(it) },
        label = { Text(text = label) },
        placeholder = { Text(text = label) },
        singleLine = true,
        enabled = !isLoginPending,
        modifier = modifier,
        keyboardOptions = keyboard?.let { keyboard } ?: KeyboardOptions.Default,
        visualTransformation = visualTransformation?.let { visualTransformation } ?: VisualTransformation.None
    )
}

我将它与视图模型一起使用来决定是否应启用登录按钮、登录用户等。 登录查看模型:

@HiltViewModel
class LoginViewModel @Inject constructor(private val repository: LoginRepository): ViewModel() {

    private val _userEmail = MutableStateFlow<String>(value = String())
    val userEmail: StateFlow<String> = _userEmail.asStateFlow()

    private val _userPassword = MutableStateFlow<String>(value = String())
    val userPassword: StateFlow<String> = _userPassword.asStateFlow()

    private val _isLoginPending = MutableStateFlow<Boolean>(value = false)
    val isLoginPending: StateFlow<Boolean> = _isLoginPending.asStateFlow()

    private val _loginError = MutableSharedFlow<String?>()
    val loginError: SharedFlow<String?> = _loginError.asSharedFlow()

    private val _isLoginButtonEnabled = MutableStateFlow<Boolean>(value = false)
    val isLoginButtonEnabled: StateFlow<Boolean> = _isLoginButtonEnabled.asStateFlow()

    fun getOnboardingStatus(): OnboardingStatus {
        return repository.isOnboardingCompleted()
    }

    fun onEvent(event: LoginEvent) {
        when (event) {
            is LoginEvent.EnteredEmail -> {
                _userEmail.value = event.email
                _isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
            }
            is LoginEvent.EnteredPassword -> {
                _userPassword.value = event.password
                _isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
            }
            is LoginEvent.Login -> {
                viewModelScope.launch {
                    _isLoginPending.value = true
                    _isLoginButtonEnabled.value = false

                    val result = repository.loginUser(_userEmail.value, _userPassword.value)
                    when (result) {
                        is AuthRequestState.Success -> { /* nothing to do */ }
                        is AuthRequestState.Fail -> {
                            _loginError.emit(result.msg)
                        }
                    }

                    _isLoginPending.value = false
                    _isLoginButtonEnabled.value = true
                }
            }
        }
    }

}

但是问题是,当我在布局检查器中的 TextFields 中输入内容时,我看到 UI 的其他元素(如 Button 和 ClickableButton)被重新组合:

如何避免此类行为?

android kotlin android-jetpack-compose android-jetpack compose-recomposition
1个回答
0
投票
@Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel) {
    val userEmail = viewModel.userEmail.collectAsState()
    val userPassword = viewModel.userPassword.collectAsState()
    val isLoginPending = viewModel.isLoginPending.collectAsState()
    val isLoginButtonEnabled = viewModel.isLoginButtonEnabled.collectAsState()

    // this line solved the recomposition problems for me.
    // replace wherever viewModel.onEvent with  onEvent 
    val onEvent:(LoginEvent) -> Unit = viewModel::onEvent

    val scaffoldState = rememberScaffoldState()

    if (viewModel.getOnboardingStatus() == OnboardingStatus.NOT_COMPLETED) {
        navController.navigate(Screen.Onboarding.route)
    }

    LaunchedEffect(key1 = true) {
        viewModel.loginError.collectLatest {
            it?.let {
                scaffoldState.snackbarHostState.showSnackbar(message = it)
            }
        }
    }

    Scaffold(
        modifier = Modifier
            .fillMaxSize()
            .recomposeHighlighter(),
        scaffoldState = scaffoldState
    ) {
        Box(modifier = Modifier
            .fillMaxSize()
            .recomposeHighlighter()) {
            if (isLoginPending.value) {
                ProgressBar(
                    modifier = Modifier
                        .align(Alignment.Center)
                        .recomposeHighlighter(),
                )
            }
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .align(Alignment.Center)
                    .recomposeHighlighter(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "Login",
                    fontWeight = FontWeight.Bold,
                    fontSize = 36.sp
                )
                Spacer(modifier = Modifier
                    .height(64.dp)
                    .recomposeHighlighter())
                UserInputTextField(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 16.dp)
                        .recomposeHighlighter(),
                    label = "Email",
                    inputState = userEmail.value,
                    onValueChange = { onEvent(LoginEvent.EnteredEmail(it)) },
                    isLoginPending = isLoginPending.value
                )
                Spacer(modifier = Modifier
                    .height(8.dp)
                    .recomposeHighlighter())
                UserInputTextField(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 16.dp)
                        .recomposeHighlighter(),
                    label = "Password",
                    inputState = userPassword.value,
                    onValueChange = { onEvent(LoginEvent.EnteredPassword(it)) },
                    isLoginPending = isLoginPending.value,
                    keyboard = KeyboardOptions(keyboardType = KeyboardType.Password),
                    visualTransformation = PasswordVisualTransformation()
                )
                Spacer(modifier = Modifier
                    .height(32.dp)
                    .recomposeHighlighter())
                Button(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(
                            start = 16.dp,
                            end = 16.dp
                        )
                        .recomposeHighlighter(),
                    enabled = isLoginButtonEnabled.value,
                    onClick = {
                        viewModel.onEvent(LoginEvent.Login)
                    }
                ) {
                    Text(text = "Login")
                }
            }
            ClickableButton(
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .padding(bottom = 32.dp)
                    .recomposeHighlighter(),
                text = "No account? Click to register",
                onTextClicked = { navController.navigate(Screen.Register.route) }
            )
        }
    }
}

@Composable
private fun UserInputTextField(
    modifier: Modifier,
    label: String,
    inputState: String,
    onValueChange: (String) -> Unit,
    isLoginPending: Boolean,
    keyboard: KeyboardOptions? = null,
    visualTransformation: VisualTransformation? = null
) {
    OutlinedTextField(
        value = inputState,
        onValueChange = { onValueChange(it) },
        label = { Text(text = label) },
        placeholder = { Text(text = label) },
        singleLine = true,
        enabled = !isLoginPending,
        modifier = modifier,
        keyboardOptions = keyboard?.let { keyboard } ?: KeyboardOptions.Default,
        visualTransformation = visualTransformation?.let { visualTransformation } ?: VisualTransformation.None
    )
}

 
© www.soinside.com 2019 - 2024. All rights reserved.