DEV Community

Arun Kumar Reddy
Arun Kumar Reddy

Posted on

Useful Kotlin Features you should know

Hi, Hope you are doing well today :)

In this blog post, I will go over some neat handy features of the Kotlin programming language which make it a delight to work with. These features help in making your code very readable and reduce unnecessary boilerplate.

Extension Functions

Extension functions in Kotlin allow you to add custom functionality to classes. For example, you can add extra functionality to any standard classes or third party dependencies which you cannot otherwise modify without deriving from them.

If you wanted to add a custom method to the String class, One solution is to extend the string class to create your own custom string class with the added functionality that you desire. You can achieve the same behavior through extension functions without actually deriving from the string class.

// Add a removeLastCharacter() extension to String class
// this keyword refers to the object on which the extension method is called
fun String.removeLastCharacter(): String {
    return this.substr(0, this.length - 1)
}
Enter fullscreen mode Exit fullscreen mode

The extension functions can simply be called as follows and the resultant output is hello kotli.

val tempString = "hello kotlin"
val newString = tempString.removeLastCharacter()
println(newString)
Enter fullscreen mode Exit fullscreen mode

Keep in mind that extension functions are resolved statically. These methods are not actually added to the class but are merely made callable via the dot notation. This means that the extension function invoked for an object is determined by its type declaration as opposed to runtime polymorphism.

Data Classes

Data classes serve a very common use case where we create classes to hold some form of data. The compiler generates default implementations for some methods such as equals(), hashCode(), toString(), copy() as well as components methods for each class attribute to allow destructuring.

data class Employee(val id: Int, val name: String)
Enter fullscreen mode Exit fullscreen mode

The generated methods for the data class work as follows

val emp = Employee(1, "Arun")
emp.toString()
// prints Employee(id=1, name=Arun)
Enter fullscreen mode Exit fullscreen mode

The properties of the data class can be destructured as

val (id, name) = emp
Enter fullscreen mode Exit fullscreen mode

A copy of the object with some properties altered while the rest remain the same can be created with the help of copy()

val newEmp = emp.copy(name = "Anurag")
newEmp.toString()
// prints Employee(id=1, name=Anurag)
Enter fullscreen mode Exit fullscreen mode

The primary constructor of the data class needs to have at least one parameter and each parameter needs to be declared as val / var depending on its mutability.

Sealed Classes

Sealed classes are effectively an extension of enums. Sealed classes have a finite set of types that its value can take and they are ideal for modelling state where the state can be an object of varying but a finite set of types.

For example, if you were implementing a data repository in an app which could return some data from the database or make a network call to fetch it from an API. You may have three types of objects that the repository could return depending on the scenario.

sealed class Response {
    data class Cached(val response: CachedResponse): Response()
    data class Network(val response: NetworkResponse): Response()
    data class Error(val errorInfo: ErrorInfo): Response()
}
Enter fullscreen mode Exit fullscreen mode

The repository API would return an object of the type Response which would be an instance of one of the above defined three types of response. Sealed classes can be combined with the when statement to handle each scenario.

when (response) {
    is Response.Network -> {
        //handle network response
    }
    is Response.Cached -> { 
        //handle cached response
    }
    is Response.Error -> { 
        //handle error info
    }
}
Enter fullscreen mode Exit fullscreen mode

Lazy by delegation

The Kotlin standard library provides native support for delegation pattern. It provides a few delegation properties which are commonly used. One such example is lazy properties which need to be initialized only when needed and then reused once created. This can be achieved through the lazy() function which takes in a lambda which serves as the delegate for implementing the lazy property and is called during the first access. The object returned by the lambda is used for subsequent calls to the property.

val lazyString: String by lazy {
   println("Creating the lazy string")
   "Lazy string"
}
println(lazyString)
println(lazyString)
Enter fullscreen mode Exit fullscreen mode

The output of the above log statements will be as follows and the lambda passed to the lazy function is only run at the time of first access and the value returned by the lambda is returned for subsequent calls.

Creating the lazy string
Lazy string
Lazy string
Enter fullscreen mode Exit fullscreen mode

I hope you found this post useful. Kotlin has lots of neat useful features which really improve the developer experience and I hope this post may have helped you to understand some of those.

Thank you

Top comments (0)