This post is focused on how I created a user registration UI form with field validation.
I will just be posting the Composable I created because this could just be implemented anywhere. There is no outside connection to it yet except for a couple of booleans to handle submission and failure on submission.
The registration UI form will validate the user input in the TextFields onValueChanged method. There are Text composables created under each TextField composable.
Android Registration Form UI
Process
- The user types in the input in the appropriate field they desire.
- The onValueChanged method is validating the form as the user types
- If the onValueChanged method updates the mutable state holding the switching boolean.
- false - then the validation composable will be displayed.
- true - then the validation composable is not display.
- If the user updates the form with validate input, then the mutable state is updated to true. This will not display the validation composable.
The validation composable contains a simple message to notify the user of the issue.
The email TextField validation uses a built in Android library to validate the email is valid.
!android.util.Patterns.EMAIL_ADDRESS.matcher(email)The password TextField validation has special requirements set for the user.
Full password syntax used in below snippet.
password.isEmpty() || password.length < 8 || !password.contains("[0-9]".toRegex()) || !password.contains("[A-Z]".toRegex()) || password.contains("[{('\"~\\[|\\]^)}]".toRegex())Code
Setup
build.gradle
build.gradle - add extended material icons to use Visibility icons. Added to make the form more visually appealing for the user. NOTE: Not necessary, you could choose different icons.
implementation(libs.androidx.material.icons.extended)libs.versions.toml
libs.versions.toml - for icons stated above. NOTE: Not needed if using icons already bundled with core Android library.
[versions]
....
materialIconsExtended = "1.7.6"
....
[libraries]
....
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
....RegisterUI
RegisterUI - main composable, I created a separate file for it to keep things organized.
@Composable
fun RegisterUI(
userRegisterFail: Boolean,
isRegisterProcessing: Boolean,
) {
var usernameValidator by remember { mutableStateOf(true) }
var emailValidator by remember { mutableStateOf(true) }
var passwordValidator by remember { mutableStateOf(true) }
var passwordVisible by remember { mutableStateOf(false) }
var username by remember { mutableStateOf("Winston") }
var email by remember { mutableStateOf("winston@clowncollege.com") }
var password by remember { mutableStateOf("Password01") }
ElevatedCard(
modifier = Modifier
.fillMaxWidth()
.padding(25.dp),
colors = CardDefaults.elevatedCardColors(MaterialTheme.colorScheme.primaryContainer)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp), horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.padding(10.dp))
Text(text = "Register", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.padding(10.dp))
TextField(value = username, onValueChange = {
username = it
if (username.isEmpty()) {
usernameValidator = false
} else {
usernameValidator = true
}
}, placeholder = { Text(text = "Choose your user name") })
if (!usernameValidator) {
Text(
text = "Invalid Username",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
Spacer(modifier = Modifier.padding(10.dp))
TextField(value = email, onValueChange = {
email = it
if (email.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email)
.matches()
) {
emailValidator = false
} else {
emailValidator = true
}
}, placeholder = { Text(text = "Enter Your Email") })
if (!emailValidator) {
Text(
text = "Invalid Email",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
Spacer(modifier = Modifier.padding(10.dp))
TextField(value = password, onValueChange = {
password = it
if (password.isEmpty() || password.length < 8 || !password.contains("[0-9]".toRegex()) || !password.contains("[A-Z]".toRegex()) || password.contains("[{('\"~\\[|\\]^)}]".toRegex())) {
passwordValidator = false
} else {
passwordValidator = true
}
}, placeholder = { Text(text = "Enter Your Password") },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff,
contentDescription = if (passwordVisible) "Hide Password" else "Show Password"
)
}
}
)
if (!passwordValidator) {
Text(
text = "Invalid Password" +
"\n-Needs to be at least 8 characters long" +
"\n-At least 1 number" +
"\n-At least 1 capital letter" +
"\n-Must Not contain one of the following " +
"special characters:" +
"\n { ( ' \" ~ [ | ] ^ ) }",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
Spacer(modifier = Modifier.padding(10.dp))
//check if form is processing info, change from button to loading circle
if (!isRegisterProcessing) {
var check = listOf(usernameValidator, emailValidator, passwordValidator)
Button(
enabled = if (check.any { !it }) false else true,
onClick = {
//submit form
}) {
Text(text = "Register")
}
} else {
CircularProgressIndicator()
}
if (userRegisterFail) {
Spacer(modifier = Modifier.padding(10.dp))
Text(
text = "Registration Error",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
}
}
}Hope this helps.
