There are different approaches when it comes to validating properties of an object in Kotlin, which indirectly involve validating UI fields if you are using this great language for Android development.

Let’s see how we can implement validation keeping in mind reusability and code readability.

For that we are going to implement two classes that are going to validate that a string contains a certain letter and that an integer is between a specified range.

Defining our validator structure

We are going to need four main actors to implement this feature:

  • ValidateWith: this is the Annotation Class which is going to be assigned to each property we would like to validate.
  • Validator: an interface for each of our validators to implement, so that we can later check if the property of the class is annotated with the ValidateWith tag.
  • ValidationError: another interface to model our errors. In my case I’m going to return an error message, but you can change it to return an Exception object for example to throw or manage later.
  • ValidatorUtils: contains the code that is going to be called to pass the object to validate. It also connects the ValidateWith annotation class with each Validator implementation.

Creating the Annotation Class

This feature is heavily going to depend on a programming concept called Reflection. I recommend that you check this StackOverflow answer if you’ve never heard of it.

As you can see in the following code snippet, we are defining an Annotation Class which takes a Validator.

@Retention
@Target(AnnotationTarget.FIELD)
annotation class ValidateWith(val validator: KClass<*>)
Kotlin

Our validator property is going to contain a reference to the validator we want to use, represented in a class, thanks to KClass.

As we are going to validate properties of a class we need to add the Target tag as a Field. You can see all available options in the official Kotlin documentation here.

Jumping into the validator code

The Validator interface is going to be implemented by each of the Validators as we will see below.

It’s only method is going to take the field that we are validating, and it’s value. Then it’s going to return a ValidationError, or null if validation is successful.

interface Validator<T> {
    fun validate(field: Field, value: T): ValidationError?
}
Kotlin

ValidationError is another interface that is going to allow us to return concrete error for each of our validations.

interface ValidationError {
    var errorMsg: String
}
Kotlin

Here you have one simple implementation that we are going to use for our validators.

data class FormatError(override var errorMsg: String) : ValidationError
Kotlin

Now that we have our interfaces and an error defined, we can now implement our validators.

You can check in the code below how we are making sure the conditions which we specified at the beginning of the article are matched, and if not, a FormatError is returned with a message.

class RangeValidator : Validator<Any?> {

    override fun validate(field: Field, value: Any?): ValidationError? =
        if ((value as Int) < 1 || value > 33) FormatError("${field.name} field is out of range (1,33)") else null

}

class FormatValidator : Validator<Any?> {

    override fun validate(field: Field, value: Any?): ValidationError? =
        if ((value as String).contains("s")) null else FormatError("${field.name} field does not contain an s")
}
Kotlin

With this approach you can inject each Validator at whatever point you are in your project, reusing the same code, avoiding the need of injection libraries.

To finish let’s see the code that we are going to call to validate an object. It has one public function and two auxiliary private ones.

The validate method is going to:

  1. Verify that the value passed is not null.
  2. Read the properties of the class that contains the fields that we want to validate thanks to Reflection.
  3. For each field, we are going to check that the ValidateWith tag is applied and that the class reference passed to it implements the Validator interface.
  4. Finally, we are going to execute each Validator. Using Reflection in the validateFields method we can call the constructor of each Validator implementation and run our code to validate the value.
object ValidatorUtils {

    fun validate(value: Any?): List<ValidationError> {
        if (value == null) throw CouldNotValidateException()
        val errors = mutableListOf<ValidationError>()
        getFields(value.javaClass).forEach { field: Field ->
            if (field.isAnnotationPresent(ValidateWith::class.java)) {
                val annotation = field.getAnnotation(ValidateWith::class.java)
                if (Validator::class.java.isAssignableFrom(annotation.validator.java)) {
                    errors += validateFields(field, FieldUtils.readField(field, value, true), annotation)
                }
            }
        }
        return errors
    }

    // Returns a list with every field inside a class
    private fun getFields(classRef: Class<*>): List<Field> {
        return classRef.declaredFields.toList()
    }

    // Calls each validator for it's associated field and returns ValidationErrors
    private fun validateFields(field: Field, value: Any, annotation: ValidateWith): List<ValidationError> {
        val validator = annotation.validator.java.getDeclaredConstructor().newInstance() as Validator<Any>
        val result = validator.validate(field, value)
        return if (result != null) {
            listOf(result)
        } else {
            emptyList()
        }
    }
}
Kotlin

In my case I’m developing this project with Gradle. With this in mind if you want to use the same code, don’t forget to add the following dependency to your build.gradle.kts file to use the FieldUtils object.

implementation("org.apache.commons:commons-text:1.10.0")
Kotlin

Running the validator

Here is a snippet to test the code above, where we just need to call the ValidatorUtils object and pass it the instantiated class we would like to validate.

class Android(
    @ValidateWith(FormatValidator::class) val brand: String?,
    @ValidateWith(RangeValidator::class) val version: Int
)

fun main() {
    val android = Android("pixs", 34)
    println(ValidatorUtils.validate(android))
}
Kotlin

If both properties of the Android class have values that are valid, the validate method is going to return an empty list, but if one or every of them fails to match the constraints established in the validators, we are going to see the following outputs.

Console output with some variations
Console output with some variations

To finish, here you have access to the repo with the code if you want to save it.

https://github.com/molidev8/annotation-classes-validation

Featured image by Philipp Katzenberger on Unsplash


If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.

Categorized in: