Kotlin tricks

Here are some interesting tips when you are using Kotlin. Let’s take a look at something about object, invoke operator, and destructuring.

object trick

The keyword object has many different functions in the Kotlin. One of its functionality is to create a singleton instance.

1
2
3
4
5
6
7
8
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}

val allDataProviders: Collection<DataProvider>
get() = // ...
}

Except the singleton object, you can use object to instantiate an anonoymous class intance, for example:

1
2
3
4
5
6
layout.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View, m: MotionEvent): Boolean {
// Perform tasks here
return true
}
})

What about a companion? You know there’s no static keyword in Kotlin world. So we have another weapon companion to do also the same thing. And, there’s one drawback for object class which is that you are not able to send a parameter to this singleton class, how can we leverage companion and object to do this?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data class SomeData(
val aString: String
)

class SomeClass {
companion object {
protected lateinit var inClassData: SomeData
fun getInstance(initData: SomeData): SomeClass {
inClassData = initData
return SomeClass()
}
}

fun doSomething(): SomeData {
println("my data : " + inClassData.aString)

// put your calculation logic here
return inClassData
}
}

So, you can easily pass in a SomeData data class into this singleton instance.

1
2
3
val myData = SomeData("data")
val singleton = SomeClass.getInstance(myData)
singleton.doSomething()

Can we do furthermore by using this concept? Yes, it gives us the ability to separate the operation logic from a refactoring class. And, it would be convenient for developers if they are refactoring their project at a particular stage.

Note that even though the members of companion objects look like static members in other languages, but they are instance members of real objects at runtime which means they can implement interfaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class SomeData()

interface Operations<T> {
fun print(initData: SomeData): T
}

class SomeClass {
companion object: Operations<SomeClass> {
protected lateinit var inClassData: SomeData
override fun print(initData: SomeData): SomeClass {
inClassData = initData
return SomeClass()
}
}

fun doSomething(): SomeData {
println("my data : " + inClassData.aString)
// put your calculation logic here
return inClassData
}
}

So, you can separate some logic from the class to an interface, that would make your code more clear. Here is another useful example to show how can we change the behavior of a singleton instance:

VehicleManager

1
2
3
4
5
6
7
object VehicleManager {
fun vehicleStatus(): String = "driving"
}

fun start(manager: VehicleManager) {
println("Current status : " + manager.vehicleStatus())
}

If you try to print out the vehicle’s status, do this:

1
start(VehicleManager)

Let’s not use the object to create a singleton instance and give the App more flexibility by using the interface:

VehicleManager Interface

1
2
3
4
5
6
7
8
9
10
interface VehicleManager {
companion object: VehicleManager {
override fun vehicleStatus(): String = "driving"
}
fun vehicleStatus(): String
}

fun start(manager: VehicleManager) {
println("Current status : " + manager.vehicleStatus())
}

You need to change to this way to use it:

1
start(VehicleManager.Companion)

By using this way, it would be easier for the developer who is refactoring the VehicleManager or can create another class inherited from VehicleManager and has more operations.

Invoke operator

Let’s see an example first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Result
abstract class Socket {
abstract fun openURL(url: String): Result
}

class SocketImpl: Socket() {
override fun openURL(url: String): Result {
return Result()
}
}

class URLHandler (socket: Socket) {

val socket: Socket = socket
operator fun invoke(url: String): Result {
println("launch socket to open URL")
return this.socket.openURL(url)
}
}

We create a URLHandler to process a specific URL that would use a Socket class to open the URL we passed in. In the URLHandler class, we implement the invoke operator function, which is unique in the Kotlin world. Let’s see what’s the tricky part here.

1
2
val urlHandler = URLHandler(SocketImpl())
urlHandler("www.google.com")

If you implement the invoke operator function, you can directly use the class instance to open the passed in URL string without any function call, which means it would automatically be invoked. It’s just like this instance to be a function.

1
30712-30712/idv.chauyan.androidcoroutine I/System.out: launch socket to open URL

Where can we find this in Kotlin? In Kotlin, you can see this:Function Interfaces

So, when you try to use the lambda function, which is defined by these interfaces, we can directly use the lambda function.

1
2
val lambda = {name: String -> "Hello, ".plus(name)}
println(lambda("chauyan"))

The result would be Hello, chauyan.

Destructuring

Kotlin supports the Destructuring feature, let’s see what’s Destructuring and what pro and con it has.

1
2
3
4
5
6
7
8
9
10
data class Person (
val id: Int,
val name: String,
val age: Int
)

fun main(args: Array) {
val person = Person(1, "test person", 40)
println("Name of Person: " + person.name);
}

We have a data class Person which has three member variables - id, name, and age. In general, if we want to access the member variables of a class, we usually use this way: person.id, person.name, etc. We can use destructuring to benefit us and write less code:

1
2
3
4
5
6
7
8
9
data class Person(
...
)

fun main(args: Array) {
val person = Person(1, "test person", 40)
val (_, name, age) = person
println("Name of Person: " + name)
}

It’s more intuitive, and you can easily understand what you get from this data class - Person. What else can we do by using destructuring?

Destructuring the return value of a function

1
2
3
4
5
6
7
8
data class Result(val error: Error, val result: Int)

fun function(...): Result {
...
return Result(error, result)
}

val (error, result) = function(...)

We can directly get the computed result from this function without assigning a variable to it. If we want to check the result quickly, we can ignore the error (not good habit, just for example) like this:

1
val (_, result) = function(...)

Destructuring in a for-loop

1
2
3
for ((key, value) in map) {
// do something with the key and the value
}

It’s more clear if we try to access something in a collection. Also, if you use lambda functions in your project, especially RxJava, you might change to use in this way:

Destructuring in a lambda function

1
2
3
4
5
// normal usage
map.mapValues { entry -> "${entry.value}!" }

// with destructuring way
map.mapValues { (key, value) -> "$value!" }

Limitations of the destructuring

When you are using the destructuring feature, you need to handle the variable types carefully. If you change the data class fields (no matter increasing or decreasing), you also need to update your destructuring declaration. In our example, Person data class, we add another field called occupation above the age as a string type.

1
2
3
4
5
6
data class Person (
val id: Int,
val name: String,
val occupation: String,
val age: Int
)

If we don’t change the previous code in the main function, you will get the wrong value:

1
2
3
4
5
6
7
fun main(args: Array) {
val person = Person(1, "test person", "engineer", 40)
val (_, name, age) = person

println("name : " + name);
println("age : " + age);
}

What is your expectation of the value of age? It should be 40, but it will print out the value - engineer. That’s the reason why if we want to change to the field, then we need to update the destructuring declaration as well. And, do we have any way to avoid this? Yes, we do.

1
val (_, name: String, age: Int) = person

Explicitly declare what kind of type you want, and let the compiler to help you.

1
'component3()' function returns 'String', but 'Int' is expected