Modtion logo
Modtion
android

Android Compose : Texfield Validation and Password

Android Compose : Texfield Validation and Password

Textfield is not perfect without any validation. Validation is needed so that the input complies with the rules and requirements and the application becomes more reliable and data consistent. As will be discussed in this article, we will discuss email and password validation which are common forms, especially for login and registration.

if you are still a beginner in jetpack compose and textfield, you can visit the first content about textfield https://www.modtion.id/posts/android-compose-textfield

Before we start code, we need to implement some library for icon and compose. In current version of compose, there is some code that don't exist in custom TextField. Open file in app/build.gradle.kts and code below and sync now.

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    implementation("androidx.compose.material:material-icons-extended")
}

We will use viewModel and create textfield component to avoid boilerplate code. Here is the code that you can use for implementing textfield validation :

ViewModel

class TextFieldViewModel: ViewModel() {

    var emailValue by mutableStateOf("")
        private set
    var emailError by mutableStateOf("")
        private set

    fun setEmail(value: String){
        emailValue = value
    }

    var passwordValue by mutableStateOf("")
        private set
    var passwordError by mutableStateOf("")
        private set

    fun setPassword(value: String){
        passwordValue = value
    }

    private fun validateEmail(): Boolean {
        val email = emailValue.trim()
        var isValid = true
        var errorMessage = ""

        if (email.isBlank() || email.isEmpty()) {
            errorMessage = "Please fill email field"
            isValid = false
        } else if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
            errorMessage = "Wrong email Format"
            isValid = false
        }

        emailError = errorMessage
        return isValid
    }

    private fun validatePassword(): Boolean {
        val password = passwordValue.trim()
        var isValid = true
        var errorMessage = ""

        if (password.isBlank() || password.isEmpty()) {
            errorMessage = "Please fill password field"
            isValid = false
        } else if (password.length < 6) {
            errorMessage = "Password must more than 6 character"
            isValid = false
        }

        passwordError = errorMessage
        return isValid
    }

    fun validateForm() {
        if (validateEmail() && validatePassword()) {
            // NEXT STEP
        }
    }
}

According the code above, there are 2 validations for email and password. For email format validation, we can use this code

Patterns.EMAIL_ADDRESS.matcher(email).matches()

TextFieldValidation Component

Texfield in compose doesn't include error message. So, we need add Text for error message manually below TextField and create handler to show that error message. This component has feature for Password TextField that will control password visibility.

@Composable
fun TextFieldValidation(
    value: String,
    placeholder: String,
    onChange: (String) -> Unit,
    isError: Boolean,
    icon: ImageVector,
    errorMessage: String,
    isPassword: Boolean = false,
    imeAction: ImeAction = ImeAction.Next,
    keyboardType: KeyboardType = KeyboardType.Text,
    modifier: Modifier = Modifier
) {

    var showPassword by rememberSaveable { mutableStateOf(false) }

    Column(
        horizontalAlignment = Alignment.Start,
        modifier = modifier.fillMaxWidth(),
    ) {
        OutlinedTextField(
            value = value,
            onValueChange = {
                if (!it.contains("\n"))
                    onChange(it)
            },
            placeholder = {
                Text(text = placeholder)
            },
            singleLine = true,
            textStyle = MaterialTheme.typography.bodyMedium,
            leadingIcon = {
                Icon(
                    icon,
                    contentDescription = "Text FieldInput",
                    tint = Color.Gray,
                    modifier = Modifier
                        .size(24.dp)
                )
            },
            trailingIcon = {
                if (isPassword){
                    Icon(
                        if (showPassword) Icons.Default.VisibilityOff else Icons.Default.Visibility,
                        contentDescription = if (showPassword) "Show Password" else "Hide Password",
                        tint = Color.Gray,
                        modifier = Modifier
                            .size(24.dp)
                            .clickable { showPassword = !showPassword }
                    )
                }else {
                    null
                }

            },
            modifier = Modifier
                .fillMaxWidth(),
            keyboardOptions = KeyboardOptions(
                keyboardType = keyboardType,
                imeAction = imeAction
            ),
            colors = OutlinedTextFieldDefaults.colors(
                unfocusedTextColor = Color.Gray,
                unfocusedBorderColor = Color.Gray,
                focusedTextColor = Color.Blue,
                focusedBorderColor = Color.Blue,
                errorBorderColor = Color.Red,
            ),
            shape = RoundedCornerShape(10.dp),
            visualTransformation = if (isPassword){
                if (showPassword) VisualTransformation.None else PasswordVisualTransformation()
            } else { VisualTransformation.None },
            isError = isError
        )
        if (isError){
            Text(
                text = errorMessage,
                style = MaterialTheme.typography.labelSmall,
                color = Color.Red,
                modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 2.dp),
                textAlign = TextAlign.Start
            )
        }
    }
}

Screen

Here is the implementation of TextFieldValidation Component for email and password. Validation will be performed when the button is pressed and clear all focus of TextField with focusManager.clearFocus()

@Composable
fun TextFieldScreen(
    viewModel: TextFieldViewModel
) {

    val focusManager = LocalFocusManager.current

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ){

        TextFieldValidation(
            value = viewModel.emailValue,
            onChange = viewModel::setEmail,
            placeholder = "Email",
            isError = viewModel.emailError.isNotEmpty(),
            icon = Icons.Rounded.Email,
            errorMessage = viewModel.emailError,
            keyboardType = KeyboardType.Email,
            modifier = Modifier
                .padding(horizontal = 24.dp)
        )
        Spacer(modifier = Modifier.height(20.dp))
        TextFieldValidation(
            value = viewModel.passwordValue,
            onChange = viewModel::setPassword,
            placeholder = "Password",
            isError = viewModel.passwordError.isNotEmpty(),
            icon = Icons.Rounded.Password,
            isPassword = true,
            errorMessage = viewModel.passwordError,
            modifier = Modifier
                .padding(horizontal = 24.dp)
        )
        Spacer(modifier = Modifier.height(20.dp))
        Button(
            onClick = {
                focusManager.clearFocus()
                viewModel.validateForm()
            },
            modifier = Modifier.padding(horizontal = 2.dp),
            shape = RoundedCornerShape(8.dp),
            colors = ButtonDefaults.buttonColors(containerColor = Blue)
        ) {
            Text(
                text = "Validate".uppercase(),
                style = MaterialTheme.typography.bodyMedium,
                color = White
            )
        }

    }
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val viewModel: TextFieldViewModel by viewModels()

        setContent {
            ModtionProjectTheme {
                TextFieldScreen(viewModel)
            }
        }
    }
}

Output

Image Image Image