Search code examples
kotlinvalidationandroid-jetpack-compose

How can I validate password and confirm password fields in jetpack compose?


How can I validate user input in two fields password and confirmPassword in jetpack compose. I tried a var matchError but it doesn't show error when the passwords don't match. How can I fix this? Also, how can i reference the password field from ConfirmPasswordTextFieldComponent function? Validation occurs when the user types in the confirmPassword fields.

@Composable
fun ConfirmPasswordTextFieldComponent(
    text: String,
    labelValue: String,
    confirmText:String, painterResource: Painter,
    onTextChanged: (String) -> Unit,
    hasError: Boolean = false,
    ) {

    val localFocusManager = LocalFocusManager.current
    val password = remember {
        mutableStateOf("")
    }

    val passwordVisible = remember {
        mutableStateOf(false)
    }
    val matchError = remember { mutableStateOf(false) }

    OutlinedTextField(
        modifier = Modifier
            .fillMaxWidth()
            .clip(componentShapes.small),
        label = { Text(text = labelValue) },
        colors = TextFieldDefaults.outlinedTextFieldColors(
            focusedBorderColor = Primary,
            focusedLabelColor = Primary,
            cursorColor = Primary,
            backgroundColor = BgColor
        ),
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Password,
            imeAction = ImeAction.Done
        ),
        singleLine = true,
        keyboardActions = KeyboardActions {
            localFocusManager.clearFocus()
        },
        maxLines = 1,
        value = password.value,
        onValueChange = {
            password.value = it
            onTextChanged(it)
        },
        leadingIcon = {
            Icon(painter = painterResource, contentDescription = "")
        },
        trailingIcon = {

            val iconImage = if (passwordVisible.value) {
                Icons.Filled.Visibility
            } else {
                Icons.Filled.VisibilityOff
            }

            val description = if (passwordVisible.value) {
                stringResource(id = R.string.hide_password)
            } else {
                stringResource(id = R.string.show_password)
            }

            IconButton(onClick = { passwordVisible.value = !passwordVisible.value }) {
                Icon(imageVector = iconImage, contentDescription = description)
            }

        },
        isError = hasError || matchError.value,
        visualTransformation = if (passwordVisible.value) VisualTransformation.None else PasswordVisualTransformation(),

    )
    Spacer(modifier = Modifier.height(8.dp))
    if (confirmText != text) {
        Text(
            text = stringResource(id = R.string.error_password_no_match),
            color = colorResource(id = R.color.colorSoftRed300),
            fontSize = 10.sp,
            fontWeight = FontWeight.Bold,
            modifier = Modifier.semantics { contentDescription = "ConfirmPasswordMessage" },
        )
        matchError.value = true
    } else {
        matchError.value = false
    }
}

PasswordTextFieldComponent:

@Composable
fun PasswordTextFieldComponent(
    labelValue: String, painterResource: Painter,
    onTextSelected: (String) -> Unit,
    errorStatus: Boolean = false
) {

    val localFocusManager = LocalFocusManager.current
    val password = remember {
        mutableStateOf("")
    }

    val passwordVisible = remember {
        mutableStateOf(false)
    }

    OutlinedTextField(
        modifier = Modifier
            .fillMaxWidth()
            .clip(componentShapes.small),
        label = { Text(text = labelValue) },
        colors = TextFieldDefaults.outlinedTextFieldColors(
            focusedBorderColor = Primary,
            focusedLabelColor = Primary,
            cursorColor = Primary,
            backgroundColor = BgColor
        ),
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Password,
            imeAction = ImeAction.Done
        ),
        singleLine = true,
        keyboardActions = KeyboardActions {
            localFocusManager.clearFocus()
        },
        maxLines = 1,
        value = password.value,
        onValueChange = {
            password.value = it
            onTextSelected(it)
        },
        leadingIcon = {
            Icon(painter = painterResource, contentDescription = "")
        },
        trailingIcon = {

            val iconImage = if (passwordVisible.value) {
                Icons.Filled.Visibility
            } else {
                Icons.Filled.VisibilityOff
            }

            val description = if (passwordVisible.value) {
                stringResource(id = R.string.hide_password)
            } else {
                stringResource(id = R.string.show_password)
            }

            IconButton(onClick = { passwordVisible.value = !passwordVisible.value }) {
                Icon(imageVector = iconImage, contentDescription = description)
            }

        },
        visualTransformation = if (passwordVisible.value) VisualTransformation.None else PasswordVisualTransformation(),
        isError = !errorStatus
    )
}

Solution

  • A simplified example for validation. We can use derivedState for this use case.

    @Composable
    fun PasswordValidationSample() {
        var password by remember {
            mutableStateOf("")
        }
        var confirmPassword by remember {
            mutableStateOf("")
        }
        val isNotMatching = remember {
            derivedStateOf {
                password != confirmPassword
            }
        }
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Top,
        ) {
            OutlinedTextField(value = password, onValueChange = { password = it })
            OutlinedTextField(value = confirmPassword, onValueChange = { confirmPassword = it })
            if (isNotMatching.value) {
                Text(text = "Passwords not matching")
            }
        }
    }