Refactoring a validation function in Android using best practices

Kacper Bąk
4 min readFeb 11, 2023

--

Photo by Scott Webb on Unsplash

Refactoring code, is my favourite thing to do lately. Thanks to the experience I’ve gained and the long-term project I’ve been developing practically from the very beginning of my adventure with mobile apps, I can work on one project all the time and improve it all the time.

This is an Android code for a Sign Up Activity in an Exchangeable Token app. The code creates an instance of FirebaseAuth for authentication and provides functionality for signing up a new user.

package com.example.exchangeabletoken.ui.authentication

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import com.example.exchangeabletoken.R
import com.example.exchangeabletoken.ui.market.MarketActivity
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase

class SignUpActivity : AppCompatActivity() {
// declare firebase
private lateinit var auth: FirebaseAuth

override fun onStart() {
super.onStart()
// create an instance of firebase
auth = FirebaseAuth.getInstance()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_up)
val email = findViewById<EditText>(R.id.email)
val password = findViewById<EditText>(R.id.password)
val confirmPassword = findViewById<EditText>(R.id.confirmPassword)
val name = findViewById<EditText>(R.id.name)
val phone = findViewById<EditText>(R.id.phone)
val address = findViewById<EditText>(R.id.postalAddress)
val signUp = findViewById<Button>(R.id.signUpButton)
signUp.setOnClickListener {
// check if user is providing email
if (email.text.toString().isEmpty()) {
Snackbar.make(it, "Please enter email", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}
// check if user is providing password
if (password.text.toString().isEmpty()) {
Snackbar.make(it, "Please enter password", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}
// check if user is providing confirm password
if (confirmPassword.text.toString().isEmpty()) {
Snackbar.make(it, "Please enter confirm password", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}

// check if user is providing name
if (name.text.toString().isEmpty()) {
Snackbar.make(it, "Please enter name", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}

// check if user with the same name doesn't already exist
val database = Firebase.database
val myRef = database.getReference("users")

myRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.hasChild(name.text.toString())) {
Snackbar.make(it, "User with the same name already exists", Snackbar.LENGTH_SHORT).show()
return
}
}

override fun onCancelled(error: DatabaseError) {
// Failed to read value
Log.w("TAG", "Failed to read value.", error.toException())
}
})

if (password.text.toString() == confirmPassword.text.toString()) {
auth.createUserWithEmailAndPassword(email.text.toString(), password.text.toString())
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// save new user's data to realtime database under their email
// save the data lowercase
myRef.child(name.text.toString().toLowerCase()).child("email").setValue(email.text.toString().toLowerCase())
myRef.child(name.text.toString().toLowerCase()).child("name").setValue(name.text.toString().toLowerCase())
myRef.child(name.text.toString().toLowerCase()).child("phone").setValue(phone.text.toString())
myRef.child(name.text.toString().toLowerCase()).child("address").setValue(address.text.toString().toLowerCase())
myRef.child(name.text.toString().toLowerCase()).child("uid").setValue(auth.currentUser?.uid.toString())
myRef.child(name.text.toString().toLowerCase()).child("balance").setValue(0)
// show that sign up is successful
Snackbar.make(it, "Sign up successful", Snackbar.LENGTH_SHORT).show()
// go to market activity
val intent = Intent(this, MarketActivity::class.java)
startActivity(intent)
} else {
Snackbar.make(it, "Sign up failed", Snackbar.LENGTH_SHORT).show()
}
}
} else {
Snackbar.make(
findViewById(android.R.id.content),
"Passwords do not match.",
Snackbar.LENGTH_SHORT
).show()
}
}
}
}

The code performs several checks to ensure that all required information is provided, including checking if the entered email and password match, and checking if the entered name already exists in the Firebase real-time database.

If all the checks pass, the code creates a new user with the provided email and password and saves the user’s information in the real-time database under their name. The saved information includes their email, name, phone number, postal address, unique identifier, and balance.

The code also uses the Snackbar component to provide visual feedback to the user if any of the checks fail.

I created just one function, that handle all the if mess.

            if (!validateInputs(email, password, confirmPassword, name, phone, address)) {
return@setOnClickListener
}
    private fun validateInputs(
email: EditText = findViewById(R.id.email),
password: EditText = findViewById(R.id.password),
confirmPassword: EditText = findViewById(R.id.confirmPassword),
name: EditText = findViewById(R.id.name),
phone: EditText = findViewById(R.id.phone),
address: EditText = findViewById(R.id.postalAddress),
): Boolean {
if (email.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please enter your email.",
Snackbar.LENGTH_SHORT
).show()
return false
}
if (password.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please enter your password.",
Snackbar.LENGTH_SHORT
).show()
return false
}
if (confirmPassword.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please confirm your password.",
Snackbar.LENGTH_SHORT
).show()
return false
}
if (name.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please enter your name.",
Snackbar.LENGTH_SHORT
).show()
return false
}
if (phone.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please enter your phone number.",
Snackbar.LENGTH_SHORT
).show()
return false
}
if (address.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
"Please enter your postal address.",
Snackbar.LENGTH_SHORT
).show()
return false
}
return true
}

But I still saw code that could be improved.

This refactored version uses two arrays: inputs and messages. The inputs array contains the EditText fields to be validated, and the messages array contains the corresponding error messages to show if the fields are empty. The code uses a for loop to iterate through the inputs array, and if an input field is empty, the corresponding error message is shown using a Snackbar.

private fun validateInputs(
email: EditText = findViewById(R.id.email),
password: EditText = findViewById(R.id.password),
confirmPassword: EditText = findViewById(R.id.confirmPassword),
name: EditText = findViewById(R.id.name),
phone: EditText = findViewById(R.id.phone),
address: EditText = findViewById(R.id.postalAddress)
): Boolean {
val inputs = arrayOf(email, password, confirmPassword, name, phone, address)
val messages = arrayOf(
"Please enter your email.",
"Please enter your password.",
"Please confirm your password.",
"Please enter your name.",
"Please enter your phone number.",
"Please enter your postal address."
)
for ((index, input) in inputs.withIndex()) {
if (input.text.toString().isEmpty()) {
Snackbar.make(
findViewById(android.R.id.content),
messages[index],
Snackbar.LENGTH_SHORT
).show()
return false
}
}
return true
}

It’s possible to replaceSnackbar with another UI component such as a Toast or a custom AlertDialog. It depends on the desired user experience and the overall design of application.

                Toast.makeText(this, messages[index], Toast.LENGTH_SHORT).show()
                // use AlertDialog
val builder = AlertDialog.Builder(this)
builder.setTitle("Error")
builder.setMessage(messages[index])
builder.setPositiveButton("OK") { dialog, which ->
dialog.dismiss()
}
builder.show()
And this is what all the messages look like at once

This article discusses code refactoring in the Kotlin language, which was used for input validation in an Android application. Programming best practices were applied, such as avoiding code repetition and improving readability. An alternative solution to the Snackbar library was also applied.

Source code: https://github.com/53jk1/ExchangeableToken

--

--