Level UP your Kotlin data class using extensions.

Kotlin provides the ability to extend a class with new functionality without having to inherit from the class or use design patterns such as Decorator. This is done via special declarations called extensions.

Models in Kotlin with data classes are already leaner and cleaner than their Java counterparts. Abstracting away all those getters, setters, toString() and copy() a method with a single keyword makes our models reflect the only thing they should be concerned about — holding data.’

A simple data class in our project is the purest form of a model we can have. I mean, look at this:

data class Podcast(    
val name: String,
val description: String,
val category: String,
val publisher: String,
val website: String
)

One quick peek and we know it’s a Podcast model and what data we need to provide to get an instance of this class.

The data keyword indicates that we have access to all those fancy methods like, copy() and toString() without any extra line of code.

In reality, however, our model classes might have to do a bit of extra work than just being data containers. Having convenient methods to format data from model class properties is a known practice.

From a bird’s eye view, the following class will probably seem fine:

data class Podcast(    
val name: String,
val description: String,
val category: String,
val publisher: String,
val website: String
) {
fun getPublisherMeta(): String = "$publisher: $website"
fun getQualifiedUrl(): String = "https://$website"
}

After all, there’s nothing wrong with having two convenient methods to print out podcast metadata. They are strongly related to the model we are working with.

However, adding utility methods inside our models tank their readability level. Our model is no longer a simple container for data. It has logic mixed up with data.

Throw in a few more methods, and we will no longer have that “one quick peek to know the data” readability.

Should we put logic inside our model classes?

Having logic inside a model class is fine. But imagine:

What if we could have some boundary between the data and logic in our models?

That would make our models stay true to their name. One separate block of code depicting a pure data entity and nothing more.

// Pure data part
data class Podcast(
val name: String,
val description: String,
val category: String,
val publisher: String,
val website: String
)

Now, the question that comes is where exactly to put any added functionality if not within the class itself. There’s a Kotlin way to this problem.

Added functionalities as extensions

We can use Kotlin’s extension functions to cut-paste logic from our data classes to top-level functions.

With this approach, our previously bloated data class breaks down into two separate units:

// Pure data part
data class Podcast(
val name: String,
val description: String,
val category: String,
val publisher: String,
val website: String
)

// Business logic part
fun Podcast.getPublisherMeta(): String = "$publisher: $website"
fun Podcast.getQualifiedUrl(): String = "https://$website"

Even if we add a hundred extension functions, from a readability standpoint, our Podcast model is still lean and a separate block of code. Also, functionality-wise, this approach is essentially the same as that we had before.

That means, we can still call getPublisherMeta() on a Podcast instance, and it will return the correct value depending on which instance we are calling the method on.

The only difference is, our model classes read better now.

We can go a step further here and extract the extension functions in a separate file. However, that kind of separation is often unnecessary.

Tech Lead | Product Design | Digital Innovation