Eliminating boilerplate code while implementing Clean Architecture

While implementing Clean Architecture, we creates different layer like domain, data and presentation. Each layer has its own model, to communicate seamlessly between layer we need to copy attribute from one type of object to another and vice-versa.

Because of which we ended up creating a different data or normal classes with almost same attribute and morpher/mapper class which can help us in copying the attribute. Object mapping is a well known term for this process.

This is tedious and error-prone task. With the help of KMorph library you can easily do it by adding few annotations. It uses annotation processing for code generation so there will be no extra overhead at runtime.

As an example suppose you two objects named as EmployeeDTO and EmployeeDAO. EmployeeDTO belongs to domain layer whereas EmployeeDAO belongs to data layer.

public class EmployeeDTO {
private String firstName;
    private String lastName;
    private BigInteger idNumber;
    // Getter and Setter
}

public class EmployeeDAO {
private String firstName;
    private String lastName;
    private BigInteger idNumber;
    // Getter and Setter
}

To copy attributes value from EmployeeDAO to EmployeeDTO and vice versa, we need to write a code something similar class shown below.

public final class EmployeeDAOMorpher {
public static EmployeeDTO morph(EmployeeDAO source) {
EmployeeDTO target = new EmployeeDTO();
target.setLastName(source.getLastName());
target.setFirstName(source.getFirstName());
target.setIdNumber(source.getIdNumber());
return target;
}
public static EmployeeDAO reverseMorph(EmployeeDTO target) {
EmployeeDAO source = new EmployeeDAO();
source.setLastName(target.getLastName());
source.setFirstName(target.getFirstName());
source.setIdNumber(target.getIdNumber());
return source;
}
}

Let the KMorph library handle this tedious and error-prone task for you. You can achieve above functionality using annotation provided by KMorph library.

Adding library to Android Project

Add the library dependency to your build.gradle file.

dependencies {
implementation 'com.kmorph:kmorph-annotation:1.0.0'
annotationProcessor 'com.kmorph:kmorph-processor:1.0.0'
}

If Android Studio is not detecting generated files then you might need to add following lines in app level build.gradle

android {
...

sourceSets {
debug.java.srcDirs += 'build/generated/source/kaptKotlin/debug'
release.java.srcDirs += 'build/generated/source/kaptKotlin/release'
}
}

Thats all you need to do for setting up the library

@MorphTo(TargetClass::class)

This annotation is used on class which needs be morphed. It expects class of target in which it should be morphed.

In order to generate the morpher, source and target class KMorph library need following thing

  • Both the classes should be of type public class i.e. interface, annotation, enum, private and protected class, abstract class can’t be used with it.
  • Attributes which you need to copy should have getter and setter for them.

KMorph library will auto compare the attributes from both the classes and creates the morpher for you. Morpher will include the attributes which has same name and compatible data types.

If attribute has different names, you can instruct KMorph library to generate mapping using @MorphToField annotation.

If attribute has different data types, you can instruct KMorph library to generate mapping using @FieldTransformer annotation.

Note : No annotation will be needed on target class.

@MorphToField(“fieldName”)

This annotation is used on attribute which has different name in both the classes. It expects attribute name from target class with whom it need be morphed.

It can be used with conjunction with @FieldTransformer annotation.

Note : This annotation should be used within class which is annotated with @MorphTo. KMorph library will ignore this annotation if used outside of class annotated by @MorphTo.

If attributes annotated with this has incompatible data type, KMorph library will ignore it.

@FieldTransformer(FieldTransformer.class)

This annotation is used on attribute which has different data type in both the classes. It expects a class which helps library to transform the fields.

Transformer class needs to implement FieldTransformerContract interface provided by KMorph library. Provide implementation for transform and reverseTransform method, library will take care of calling appropriate method.

class MillisToDateStringTransformer : FieldTransformerContract<String, Long> {
private val DATE_FORMAT = "dd MMM yyyy"

override fun reverseTransform(target: Long): String {
return SimpleDateFormat(DATE_FORMAT).format(Date(target))
}

override fun transform(source: String): Long {
return try {
SimpleDateFormat(DATE_FORMAT).parse(source).time
} catch (e: ParseException) {
(-1).toLong()
}
}
}

It can be used with conjunction with @MorphToField annotation.

Copying data between two classes

This library can copy data between

  1. Two data classes
  2. Two normal classes
  3. One normal and one data class
  4. Two data classes with different number of variables
  5. Two normal classes with different number of variables

Let’s consider a scenario where you need to copy data between two normal classes

@MorphTo(EmployeeDTO::class)
class EmployeeDAO {
var firstName: String = ""
var lastName: String = ""
var idNumber: BigInteger = BigInteger("-1")
var phone: String = ""
var email: String = ""
var jobTitle: String = ""
@MorphToField("departmentName")
@FieldTransformer(DepartmentIdToNameTransformer::class)
var departmentId: Int = 0
var supervisor: String = ""
@MorphToField("workLocation")
var location: String = ""
var employeeType: Int = 0
var active: Boolean = false
@FieldTransformer(MillisToDateStringTransformer::class)
var startDate: Long = 0
@FieldTransformer(MillisToDateStringTransformer::class)
var endDate: Long = 0
var documentLink: String = ""
}

class EmployeeDTO {
var lastName: String = ""
var firstName: String = ""
var idNumber: BigInteger = BigInteger("-1")
var phone: String = ""
var email: String = ""
var jobTitle: String = ""
var departmentName: String = ""
var supervisor: String = ""
var workLocation: String = ""
var employeeType: Int = 0
var startDate: String = ""
var endDate: String = ""
var documentLink: String = ""
var active: Boolean = false
}

As location from EmployeeDAO needs to copied into workLocation of EmployeeDTO, I have used @MorphToField annotation.

startDate in both the classes has different data type, to copy there value I have used @FieldTransformer annotation. Created MillisToDateStringTransformer class for conversion of it and passed it as parameter to the annotation.

Now if you rebuild the project, you will find a Kotlin file named as EmployeeDAOMorpher.kt. Inside this there will be two extension written for copying values from EmployeeDAO to EmployeeDTO and vice-versa.

fun EmployeeDAO.morphToEmployeeDTO(): EmployeeDTO {
val target = EmployeeDTO()
target.departmentName = com.kmorph.DepartmentIdToNameTransformer().transform(departmentId)
target.lastName = lastName
target.endDate = com.kmorph.MillisToDateStringTransformer().reverseTransform(endDate)
target.jobTitle = jobTitle
target.active = active
target.idNumber = idNumber
target.workLocation = location
target.firstName = firstName
target.employeeType = employeeType
target.phone = phone
target.documentLink = documentLink
target.email = email
target.supervisor = supervisor
target.startDate = com.kmorph.MillisToDateStringTransformer().reverseTransform(startDate)
return target
}
fun EmployeeDTO.morphToEmployeeDAO(): EmployeeDAO {
val source = EmployeeDAO()
source.departmentId = com.kmorph.DepartmentIdToNameTransformer().reverseTransform(departmentName)
source.lastName = lastName
source.endDate = com.kmorph.MillisToDateStringTransformer().transform(endDate)
source.jobTitle = jobTitle
source.active = active
source.idNumber = idNumber
source.location = workLocation
source.firstName = firstName
source.employeeType = employeeType
source.phone = phone
source.documentLink = documentLink
source.email = email
source.supervisor = supervisor
source.startDate = com.kmorph.MillisToDateStringTransformer().transform(startDate)
return source
}

Now simply by calling this method on your object, you can easily copy data in different type of object.

Java version of this library is also available, you can find it at