Skip to main content

Command Palette

Search for a command to run...

Android Jetpack Compose | Register page fields Validation using ViewModel in MVVM

Updated
7 min read
Android Jetpack Compose | Register page fields Validation using ViewModel in MVVM
K

I have hands-on 6 years of experience in Android development with Java + Kotlin, Experience in most trending Android functionalities, UI Designs, Reusable Components, MVVM & MVI architectures, Testing, Bug Fixes, and self & quick learning skills.

My expertise includes:

  • Proficiency in Kotlin & Java Both
  • Strong understanding of app architecture Like MVVM, Clean Architecture
  • Experience with Jetpack Compose and coroutines and RxJava
  • Collaboration with designers and product managers
  • Commitment to delivering high-quality, testable code

TECHNICAL SKILL SET Kotlin | Java | Jetpack Compose | Kotlin Multiplatform Platform (KMP) | Kotlin Flow | Firebase | Sqlite | Room/Realm database | Kotlin Coroutine | REST API | Web Service | MVVM/MVI/ Clean Architecture | Multiple screen support | JUnit/Mockito Unit Testing | Espresso UI Testing | Instrumental Testing | Version Controlling | Git | Jenkins CI/CD | Retrofit | Dagger/Hilt | BLE | CameraX | IOT

Hello guys, Hope you are coding well.

I am developing a chat app using Firebase in a series and today I'm gonna show you how we can validate fields using ViewModel in Android Jetpack compose, Which is very common right?

In this tutorial, we'll build a chat app using Firebase and demonstrate how to validate fields in real-time using ViewModel in Android Jetpack Compose. We'll cover creating a new project, adding necessary dependencies, setting up a data class and ViewModel, and implementing validation logic for username, email, password, and confirm password fields. Finally, we'll create a Register Activity to integrate these components and ensure a seamless user registration experience.

So, in this tutorial, we will see how we can validate fields in real-time like our username is correct or our email is correctly entered or not, and Our password and confirm password matches correctly.

Here you can take any of the conditions to check like, our password should contain a capital letter, a special character, or at least 7 characters long. Any of these conditions would be helpful as per your requirements.

Okay then, Let's Code.

Step 1: Create a new project by selecting Empty-Compose-Activity

Step 2: Add ViewModelCompose dependency, so we can use viewmodels in our app.

implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0'

Step 3: Now create a data class, In my case, it's RegisterUser class.

data class RegisterUser(
    var name: String = "",
    var email: String = "",
    var password: String = "",
    var confirmPassword: String = ""
)

Step:4 Now it's time to create our ViewModel

Here I am creating so many variables to ensure every value is initialized and can be used at the appropriate attributes. So Let's see here.

package com.kuldeep.gemini_chat_gpt.ViewModels

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.kuldeep.gemini_chat_gpt.Models.RegisterUser

class RegisterViewModel : ViewModel() {

    var regUser: RegisterUser = RegisterUser()
    val fireBaseViewModel = FireBaseViewModel()

    var isLoading : Boolean = false

    var userName: MutableState<String> = mutableStateOf(regUser.name)
    var isUserNameValid: MutableState<Boolean> = mutableStateOf(false)
    var userNameErrMsg: MutableState<String> = mutableStateOf("")

    var email: MutableState<String> = mutableStateOf(regUser.email)
    var isEmailValid: MutableState<Boolean> = mutableStateOf(false)
    var emailErrMsg: MutableState<String> = mutableStateOf("")

    var password: MutableState<String> = mutableStateOf(regUser.password)
    var isPasswordValid: MutableState<Boolean> = mutableStateOf(false)
    var passwordErrMsg: MutableState<String> = mutableStateOf("")

    var confirmPassword: MutableState<String> = mutableStateOf(regUser.confirmPassword)
    var isConfirmPasswordValid: MutableState<Boolean> = mutableStateOf(false)
    var confPasswordErrMsg: MutableState<String> = mutableStateOf("")

    var isEnabledRegisterButton: MutableState<Boolean> = mutableStateOf(false)

    private fun shouldEnabledRegisterButton() {
        isEnabledRegisterButton.value = userNameErrMsg.value.isEmpty()
                && emailErrMsg.value.isEmpty()
                && passwordErrMsg.value.isEmpty()
                && confPasswordErrMsg.value.isEmpty()
                && userName.value.isNotEmpty()
                && email.value.isNotEmpty()
                && password.value.isNotEmpty()
                && confirmPassword.value.isNotEmpty()
    }

    fun validateUserName() {
        if (userName.value.length >= 10) {
            isUserNameValid.value = true
            userNameErrMsg.value = "User Name should be less than 10 chars"
        } else {
            isUserNameValid.value = false
            userNameErrMsg.value = ""
        }
        shouldEnabledRegisterButton()
    }

    fun validateEmail() {
        if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email.value).matches()) {
            isEmailValid.value = true
            emailErrMsg.value = "Input proper email id"
        } else {
            isEmailValid.value = false
            emailErrMsg.value = ""
        }
        shouldEnabledRegisterButton()
    }

    fun validatePassword() {
// Here just like this condition we can set as per our requirements.
      /*  if (password.value != "123456") {
            isPasswordValid.value = true
            passwordErrMsg.value = "Password should be 123456"
        } else {
            isPasswordValid.value = false
            passwordErrMsg.value = ""
        }*/
        shouldEnabledRegisterButton()
    }

    fun validateConfirmPassword() {
        if (password.value != confirmPassword.value) {
            isConfirmPasswordValid.value = true
            confPasswordErrMsg.value = "Password did not match"
        } else {
            isConfirmPasswordValid.value = false
            confPasswordErrMsg.value = ""
        }
        shouldEnabledRegisterButton()
    }

    fun register() {
        regUser.name = userName.value
        regUser.email = email.value
        regUser.password = password.value
        regUser.confirmPassword = confirmPassword.value

        isLoading = true
    }

}

In the above code, you can see I have made so many state variables for the value, ifItIsValid, and for Error. You can create a separate state class for each of them and maintain it as its property. Let me show you.

data class username(
    var value : String,
    var isValid : Boolean,
    var errMsg : String
) // We can create for email, password & ConfirmPassword just like this. But 
// to show you in a simple way, I have created separate variables.

In the view model, I have also created some functions to check validations, Also when the register button is visible (On what condition).

Here we can modify some of the conditions as per our need but I would like to take your suggestions here on ViewModel. Open to suggestions. 🤞😊

Step 5: Create Register Activity

package com.kuldeep.gemini_chat_gpt.Screens

class RegisterActivity : ComponentActivity() {
    val fireBaseViewModel: FireBaseViewModel by viewModels()

    @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        val registerViewModel: RegisterViewModel by viewModels()
        setContent {
            Gemini_chat_gptTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) {
                    if (registerViewModel.isLoading) ShowLoadingScreen()
                    else RegisterScreen(registerViewModel)
                }
            }
        }
    }

    @Composable
    fun RegisterScreen(rvm: RegisterViewModel) {
        val keyboardController = LocalSoftwareKeyboardController.current
        val context = LocalContext.current

        Card(
            elevation = 10.dp,
            modifier = Modifier
                .fillMaxWidth(1f)
                .fillMaxHeight(1f)
                .shadow(8.dp)
                .padding(5.dp),
            shape = RoundedCornerShape(8.dp)
        ) {
            val keyboardController = LocalSoftwareKeyboardController.current
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Image(
                    painter = painterResource(id = R.drawable.ai),
                    contentDescription = "App Icon",
                    modifier = Modifier
                        .width(100.dp)
                        .height(100.dp)
                )
                Box(modifier = Modifier.height(5.dp))
                androidx.compose.material.Text(
                    text = registerText,
                    color = Gray900,
                    style = TextStyle(
                        fontSize = MaterialTheme.typography.titleLarge.fontSize,
                        fontWeight = FontWeight.Bold,
                    )
                )
                showUserName(rvm = registerViewModel)
                showEmail(rvm = registerViewModel)
                showPassword(rvm = registerViewModel)
                showConfirmPassword(rvm = registerViewModel)
                showRegisterButton(rvm = registerViewModel, context = context)
                showLoginButton(rvm = registerViewModel, context = context)
            }
        }

    }

    @Composable
    fun showUserName(rvm: RegisterViewModel) {
        OutlinedTextField(
            value = rvm.userName.value,
            shape = RoundedCornerShape(25.dp),
            onValueChange = { newText ->
                rvm.userName.value = newText
                rvm.validateUserName()
            },
            isError = rvm.isUserNameValid.value,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 5.dp),
            label = {
                androidx.compose.material.Text(
                    text = nameText,
                    color = Gray900
                )
            },
            singleLine = true,
            colors = TextFieldDefaults.outlinedTextFieldColors(
                unfocusedBorderColor = MaterialTheme.colorScheme.primary,
                textColor = MaterialTheme.colorScheme.tertiary
            ),
            keyboardOptions =
            KeyboardOptions(keyboardType = KeyboardType.Text)
        )
        Text(
            modifier = Modifier.padding(start = 8.dp),
            text = rvm.userNameErrMsg.value,
            fontSize = 14.sp,
            color = Color.Red
        )
        Box(modifier = Modifier.height(5.dp))
    }

    @Composable
    fun showEmail(rvm: RegisterViewModel) {
        OutlinedTextField(
            value = rvm.email.value,
            shape = RoundedCornerShape(25.dp),
            onValueChange = { newText ->
                rvm.email.value = newText
                rvm.validateEmail()
            },
            isError = rvm.isEmailValid.value,
            singleLine = true,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 5.dp),
            label = {
                androidx.compose.material.Text(
                    text = emailText,
                    color = Gray900
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                unfocusedBorderColor = MaterialTheme.colorScheme.primary,
                textColor = MaterialTheme.colorScheme.tertiary
            ),
            keyboardOptions =
            KeyboardOptions(keyboardType = KeyboardType.Email)
        )
        Text(
            modifier = Modifier.padding(start = 8.dp),
            text = rvm.emailErrMsg.value,
            fontSize = 14.sp,
            color = Color.Red
        )
        Box(modifier = Modifier.height(5.dp))
    }

    @Composable
    fun showPassword(rvm: RegisterViewModel) {
        OutlinedTextField(
            value = rvm.password.value,
            shape = RoundedCornerShape(25.dp),
            onValueChange = { newText ->
                rvm.password.value = newText
                rvm.validatePassword()
            },
            isError = rvm.isPasswordValid.value,
            singleLine = true,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 5.dp),
            label = {
                androidx.compose.material.Text(
                    text = passwordText,
                    color = Gray900
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                unfocusedBorderColor = MaterialTheme.colorScheme.primary,
                textColor = MaterialTheme.colorScheme.tertiary
            ),
            keyboardOptions =
            KeyboardOptions(keyboardType = KeyboardType.Password)
        )
        Text(
            modifier = Modifier.padding(start = 8.dp),
            text = rvm.passwordErrMsg.value,
            fontSize = 14.sp,
            color = Color.Red
        )
        Box(modifier = Modifier.height(5.dp))
    }

    @Composable
    fun showConfirmPassword(rvm: RegisterViewModel) {
        OutlinedTextField(
            value = rvm.confirmPassword.value,
            shape = RoundedCornerShape(25.dp),
            onValueChange = { newText ->
                rvm.confirmPassword.value = newText
                rvm.validateConfirmPassword()
            },
            isError = rvm.isConfirmPasswordValid.value,
            singleLine = true,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 5.dp),
            label = {
                androidx.compose.material.Text(
                    text = confPasswordText,
                    color = Gray900
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                unfocusedBorderColor = MaterialTheme.colorScheme.primary,
                textColor = MaterialTheme.colorScheme.tertiary
            ),
            keyboardOptions =
            KeyboardOptions(keyboardType = KeyboardType.Password)
        )
        Text(
            modifier = Modifier.padding(start = 8.dp),
            text = rvm.confPasswordErrMsg.value,
            fontSize = 14.sp,
            color = Color.Red
        )
        Box(modifier = Modifier.height(5.dp))
    }

    @Composable
    fun showRegisterButton(rvm: RegisterViewModel, context: Context) {
        OutlinedButton(
            modifier = Modifier
                .height(56.dp),
            onClick = {
                rvm.register()
                val profile = Profile(
                    displayName = rvm.regUser.name,
                    email = rvm.regUser.email,
                    password = rvm.regUser.password,
                    image = ""
                )
                fireBaseViewModel.signUp(profile)
                fireBaseViewModel.registrationStatus.observe(this, Observer {
                    when (it) {
                        is ResultOf.Success -> {
                            rvm.isLoading = false
                            startActivity(Intent(this, UserListActivity::class.java))
                        }

                        is ResultOf.Failure -> {
                            val failedMessage = it.message ?: "Unknown Error"
                            println(failedMessage)
                            Toast.makeText(
                                this,
                                "Registration failed with $failedMessage",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }
                })

            },
            enabled = rvm.isEnabledRegisterButton.value,
//                    enabled = true,
            shape = RoundedCornerShape(50),
            elevation = ButtonDefaults.elevation(10.dp),
            border = BorderStroke(1.dp, Gray50),
            colors = ButtonDefaults.buttonColors(Gray900)
        ) {
            Box(modifier = Modifier.width(100.dp))
            Text(text = registerText, color = Gray50)
            Box(modifier = Modifier.width(100.dp))
        }
        Box(modifier = Modifier.height(5.dp))
    }

    @Composable
    fun showLoginButton(rvm: RegisterViewModel, context: Context) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(15.dp),
            horizontalArrangement = Arrangement.SpaceEvenly,
        ) {
            ClickableText(
                text = AnnotatedString(loginHereText),
                style = TextStyle(
                    fontSize = MaterialTheme.typography.bodyLarge.fontSize,
                    fontWeight = FontWeight.Bold,
                    color = Gray900
                ),
                onClick = { offset ->
                    startActivity(Intent(this@RegisterActivity, LoginActivity::class.java))
                }
            )
        }
    }

    @Composable
    fun ShowLoadingScreen(){
        return Surface(modifier = Modifier.fillMaxSize()) {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Indicator()
            }
        }
    }


    val registerViewModel: RegisterViewModel by viewModels()

    @Preview(showBackground = true)
    @Composable
    fun GreetingPreview4() {
        Gemini_chat_gptTheme {
            RegisterScreen(registerViewModel)
        }
    }
}

And at last,

Step 6: Call this activity from LoginActivity. That's it.

Lemme show you some images of my project. You can get a better idea.

In the first image, you can see when we type something in the username, it will check that the username should be 7 characters long, the same way it will check whether the email is correct or not, and for the password and confirm whether the password is same or not.

More from this blog

Kuldeep Jindani Developer's Guide

6 posts

Hello Coders, I am an engineer and having 7 years of experience in Android Development, Currently Working with Kotlin, MVVM, Jetpack Compose, and Kotlin Multiplatform.