Top 12 Advanced Kotlin Tips For Pro Developers

1
3600
Top 12 Advanced Kotlin Tips For Pro Developers
Top 12 Advanced Kotlin Tips For Pro Developers

Advanced Kotlin Tips on writing good Kotlin code and using what the language has to offer. There are many benefits of using Kotlin; it is concise, safe and most importantly it is 100% interoperable with Java. It also tries to solve some of the limitations of Java.

Now is a good time to consider Kotlin for your next big feature or project, because Google finally announced it to be a first-class language for writing Android apps in Google I/O 17.

So here are some advanced Kotlin tips to help you start making use of what the language has to offer and writing good Kotlin code.

1Singleton

Implementing lazy loaded and thread-safe singletons in Kotlin is really easy, unlike Java where you would have to rely on the complex double-checked locking pattern. And although Java enum singletons are thread-safe, they are not lazy loaded.

You must see this :Kotlin Parcelize – Developer need to know

object Singleton {
    var s: String? = null
}

Contrary to a Kotlinclass, an object can’t have any constructor, but initblocks can be used if some initialization code is required.

Singleton.s = "test" // class is initialized at this point

 

The object will be instantiated and its init blocks will be executed lazily upon first access, in a thread-safe way.

2Utility Functions

Favor Kotlin top-level extension functions over the typical Java utility classes. And for easier consumption within Java code, use @file:JvmName to specify the name of the Java class which would get generated by the Kotlin compiler.

Fun with KotlinJS

// Use this annotation so you can call it from Java code like StringUtil.
@file:JvmName("StringUtil")
fun String.lengthIsEven(): Boolean = length % 2 == 0
val lengthIsEven = "someString".lengthIsEven()

3Object Initialization

Use apply to group object initialization statements to allow for cleaner, easier to read code.

// Don't 
val textView = TextView(this)
textView.visibility = View.VISIBLE
textView.text = "test"
// Do
val textView = TextView(this).apply {
    visibility = View.VISIBLE
    text = "test"
}

4Some more small Kotlin tips just for you!

let()

Using let() can be a concise alternative for if. Check out the following code:

Disadvantage of Kotlin

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    if (item != null) {
        println(item!!)
    }
}

With let(), there is no need for an if.

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) } // prints A and ignores null
}

when()

when replaces the switch operator of Java and it can also be used to clean up some hard to read if conditions.

// Java
public Product parseResponse(Response response) {
   if (response == null) {
       throw new HTTPException("Something bad happened");
   }
   int code = response.code();
   if (code == 200 || code == 201) {
       return parse(response.body());
   }
   if (code >= 400 && code <= 499) {
       throw new HTTPException("Invalid request");
   }
   if (code >= 500 && code <= 599) {
       throw new HTTPException("Server error");
   }
   throw new HTTPException("Error! Code " + code);
}

And in Kotlin it would look like:

// Kotlin
fun parseResponse(response: Response?) = when (response?.code()) {
   null -> throw HTTPException("Something bad happened")
   200, 201 -> parse(response.body())
   in 400..499 -> throw HTTPException("Invalid request")
   in 500..599 -> throw HTTPException("Server error")
   else -> throw HTTPException("Error! Code ${response.code()}")
}

5Read-only lists, maps, …

Kotlin distinguishes between mutable and immutable collections (lists, sets, maps, etc). Precise control over exactly when collections can be edited is useful for eliminating bugs, and for designing good APIs.

The Kotlin standard library contains utility functions and types for both mutable and immutable collections. For example listOf(), and mutableListOf().

val list = listOf(“a”, “b”, “c”)
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

6Lazy property

lazy is good, use it when it makes sense! Keeping a low memory footprint is a good use case, and also saving CPU cycles if its initialization is expensive.

Lazy properties only get computed upon first access.

val str: String by lazy {
     // Compute the string
}

7Fight the temptation to squeeze everything in a single expression

It can be quite tempting to try to squeeze everything into a single expression. Just because you can, doesn’t mean that it’s a good idea.

If a single expression function is forced to wrap to a new line it should be a standard function.

Aim for readable and clean code instead.

8Local functions

Local functions are good for code reuse, just be careful not to overuse them to avoid confusion.

fun foo(a: Int) {
    fun local(b: Int) {
        return a + b
    }
    return local(1)
}

9Infix functions

Infix functions are good for readability, because it allows typing something like “test” foo “x” for example, pretty cool eh!

infix fun String.foo(s: String) {
    ...
}
// Call extension function.
"test".foo("x")
// Or call extension function using infix notation.
"test" foo "x"

Infix functions must have a single parameter.

10Inline functions

A lambda expression in Kotlin is translated to Java anonymous classes in Java 6 or 7, that is an overhead. Lambda calls are affecting the call stack which has a performance impact.

inline functions can be used to flat out calls instead of invoking another method call and adding that to the call stack. So it makes sense to use inline functions when we pass in the lambdas.

inline fun callBlock(block: () -> Unit) {
    println("Before calling block")
    block()
    println("After calling block")
}

When we call callBlock it gets translated to something like the following:

callBlock { println("The block operation") }
// Rough java bytecode
String var1 = "Before calling block";
System.out.println(var1)
String var2 = "The block operation";
System.out.println(var2);
var1 = "After calling block";
System.out.println(var1);

vs. the following if the function was not marked as inline

callBlock { println("The block operation") }
// Rough java bytecode
callBlock((Functinos0)null.INSTANCE);

You have to be careful with inline functions though because it literary copies the method content where it is called and if the body of the functions is too large you really do not want to do this.

Knowing that, the following obviously will not make any sense because it has zero effect.

inline fun foo(noinline block: () -> Unit) {// Single lambda marked as noinline
inline fun foo() { // No lambdas

11Tail recursion

By using tailrec we let the compiler know that it can replace the method call with a for loop or goto statement.

You can only use it if the last call in a function is only calling itself and only itself.

tailrec fun findFixPoint(x: Double = 1.0): Double         
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

12Sealed classes

According to Kotlin reference we should use sealed classes for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.

In other words, they are good when returning different but related types.

sealed class Response 
data class Success(val content: String) : Response() 
data class Error(val code: Int, val message: String) : Response() 
fun getUrlPage(url: String) : Response {
    val valid = // Some logic here!
    if (valid) {
        return Success("Content")
    else {
        return Error(404, "Not found")
    }
}
// Here is the beauty of it.
val response = getUrlPage("/")
when (response) {
     is Success -> println(response.content)
     is Error -> println(response.message)
}

Sealed classes must be defined in a single file.

13Some more small tips just for you!

Local return

They are mainly helpful with lambdas, but let us explain it with a basic code example. What do you think the first return in the following code does?

fun foo(list: List<String>): Boolean {
    list.forEach {
        if (...) {// Some condition.
           return true
        }
    }
    return false
}

It gets foo to return true. You can also choose to only return from the forEach scope.

fun foo(list: List<String>): Boolean {
    list.forEach {
        if (...) {// Some condition.
           return@forEach // Just like calling break
        }
    }
    return false
}

If that does not make much sense, check out the following code:

fun foo() {
    Observable.just(1)
                .map{ intValue ->
                    return@map intValue.toString() 
                }
                ...
}

If we used return in the above code snippet, it would return to foo which does not make much sense here. return@map though returns result of the map function which is the intended behaviour.

Operator overloading

Use operator to override supported operators.

operator fun plus(time: Time) {
    ...
}
// This will allow the following statement.
time1 + time2

Be careful not to overuse operator overloading, it does not make sense to use it most of the time. Check out the list of the conventions that regulate operator overloading for different operators.

Lambda extensions

They are just nicer to read and type, just like markup.

class Status(var code: Int, var description: String)
fun status(status: Status.() -> Unit) {}
// This will allow the following statement
status {
   code = 404
   description = "Not found"
}

lateinit

lateinit property will throw an exception of type UninitializedPropertyAccessException if there was an attempt to use it before it got initialized.

Companion Objects

Companion Objects are the closest thing to java static methods.

class MyClass {
  @Jvmstatic
  companion object Factory {
    fun create(): MyClass = MyClass()
  }
}

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = MyClass.create()

I would recommend using extensions though with @file:JvmName(“SomethingUtil”) and @file:JvmMultifileClass when you have multiple kotlin files that you want to merge in on java util class.

If you enjoyed reading this story, please comment and share it to help others find it!