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 | object DataProviderManager { |
Except the singleton object, you can use object
to instantiate an anonoymous class intance, for example:
1 | layout.setOnTouchListener(object : View.OnTouchListener { |
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 | data class SomeData( |
So, you can easily pass in a SomeData
data class into this singleton instance.
1 | val myData = SomeData("data") |
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 | data class SomeData() |
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 | object VehicleManager { |
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 | interface VehicleManager { |
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 | class Result |
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 | val urlHandler = URLHandler(SocketImpl()) |
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 | val lambda = {name: String -> "Hello, ".plus(name)} |
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 | data class Person ( |
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 | data class Person( |
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 | data class Result(val error: Error, val result: Int) |
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 | for ((key, value) in map) { |
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 | // normal usage |
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 | data class Person ( |
If we don’t change the previous code in the main function, you will get the wrong value:
1 | fun main(args: Array) { |
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 |