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 eachValidator
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<*>)
KotlinOur 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?
}
KotlinValidationError
is another interface that is going to allow us to return concrete error for each of our validations.
interface ValidationError {
var errorMsg: String
}
KotlinHere you have one simple implementation that we are going to use for our validators.
data class FormatError(override var errorMsg: String) : ValidationError
KotlinNow 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")
}
KotlinWith 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:
- Verify that the value passed is not null.
- Read the properties of the class that contains the fields that we want to validate thanks to Reflection.
- For each field, we are going to check that the
ValidateWith
tag is applied and that the class reference passed to it implements theValidator
interface. - Finally, we are going to execute each
Validator
. Using Reflection in thevalidateFields
method we can call the constructor of eachValidator
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()
}
}
}
KotlinIn 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")
KotlinRunning 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))
}
KotlinIf 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.

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 and support me, don’t forget to check the rest of the bolg or subscribe here to get an email every time I publish new content.