Handsome codes with Kotlin

Kotlin is a very flexible programming language. You have a chance to write more readable and good-looking code. The only limit is your imagination and creativity. I will give you some examples that I use but first, let’s look at the features of Kotlin which may be useful for us.

What are the features of Kotlin to make codes handsome?

As its name implies, extension functions provide us to extend features of a class. You may add another behaviour into a class without inheriting it. To write good-looking code, naming is the key factor. Imagine that you have a class which is not under your control. It comes from an third party API that you cannot make changes. The authors of this class have given bad names to methods of it. You may add extension functions with better names and use them to make your code more readable.

I did not like to write editText.getText().toString() to get the text of EditText as string. It is too long. Let’s simplify this and add an extension function into EditText.

fun EditText.asString(): String {
return text.toString()
}

editText.asString() is more handsome now.

Higher-order Functions

A higher-order function is a function that takes another function as a parameter. It means you can pass a function into a function in Kotlin. Let’s look at the following sample.

inline fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.func()
editor.apply()
}

We can save data in SharedPreferences like below.

val prefs = defaultSharedPreferences
prefs.edit {
putString("foo", "bar")
putInt("id", 1)
}

func: SharedPreferences.Editor.() is a function parameter which is belonged to SharedPreferences.Editor class. In other words, you can pass methods of SharedPreferences.Editor class. Higher-order functions allows us to reduce boilerplate code and gives the flexibility.

It looks better and simpler. Let’s make it more handsome. Add the following extension function into SharedPreferences.Editor class.

fun SharedPreferences.Editor.put(pair: Pair<String, Any>) {
val key = pair.first
val value = pair.second
when(value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
else -> error("Only primitive types can be stored in SharedPreferences")
}
}

Now, we can use it like below.

val prefs = defaultSharedPreferences
prefs.edit {
put("foo" to "bar")
put("id" to 1)
}

It looks handsome, doesn’t it?

Now, let’s continue with a complex example

Optional callback classes

We can use lambdas for functions which has a interface parameter with one callback method like button.setOnClickListener(view -> doSomething()) or you can also write like button.setOnClickListener { doSomething() }. However, what if we have more than one callback method in our interface? We still have to implement interface and pass parameter as anonymous inner classes like we do in Java before. There is an example below.

animation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
// actually, I don't need this method but I have to implement this.
}

override fun onAnimationEnd(animation: Animation?) {
doSomething()
}

override fun onAnimationStart(animation: Animation?) {
// actually, I don't need this method but I have to implement this.
}
})

You have to implement two methods which are unused. It seems ugly and messy. We can make it more handsome with using higher-order functions and extension functions.

Let’s start. First of all, We have to write a class that implements Animation.AnimationListener.

class __AnimationListener : Animation.AnimationListener {
private var _onAnimationRepeat: ((animation: Animation?) -> Unit)? = null
private var _onAnimationEnd: ((animation: Animation?) -> Unit)? = null
private var _onAnimationStart: ((animation: Animation?) -> Unit)? = null

override fun onAnimationRepeat(animation: Animation?) {
_onAnimationRepeat?.invoke(animation)
}

fun onAnimationRepeat(func: (animation: Animation?) -> Unit) {
_onAnimationRepeat = func
}

override fun onAnimationEnd(animation: Animation?) {
_onAnimationEnd?.invoke(animation)
}

fun onAnimationEnd(func: (animation: Animation?) -> Unit) {
_onAnimationEnd = func
}

override fun onAnimationStart(animation: Animation?) {
_onAnimationStart?.invoke(animation)
}

fun onAnimationStart(func: (animation: Animation?) -> Unit) {
_onAnimationStart = func
}

}

We have three properties which are actually functions. They will be invoked when original callback methods are triggered. I have also overloaded methods of original callback methods to set function properties.

Then, we need a extension function that will be sibling of Animation#setAnimationListener(listener: AnimationListener).

inline fun Animation.setAnimationListener(
func: __AnimationListener.() -> Unit) {
val listener = __AnimationListener()
listener.func()
setAnimationListener(listener)
}

It takes functions of __AnimationListener class as a parameter.

Finally, we can set our listener as in the following example.

animation.setAnimationListener {
onAnimationRepeat {
// do something
}

onAnimationEnd {
// do something
}

onAnimationStart {
// do something
}
}

Wow, it looks handsome. :)

If you need only one of these callback methods, you can omit the other ones like below. They are optional.

animation.setAnimationListener {
onAnimationEnd {
// do something
}
}

Our function properties are nullable. When AnimationListener#onAnimationRepeat(animation: Animation) are triggered, _onAnimationRepeat won’t be invoked because we did not set it.

There is another example of optional callback classes. I usually use Picasso library to load image from url in Android. We can pass a callback in order to get to know image loaded successfully as the following example.

Picasso.with(context).load(imageUrl).into(imageView,
object : Callback {
override fun onSuccess() {
animateImageView()
}

override fun onError() {
// empty method. (looks ugly)
}
})

In order to make it clear. We can create an optional callback class like below.

class __Callback : Callback {

private var _onSuccess: (() -> Unit)? = null
private var _onError: (() -> Unit)? = null

override fun onSuccess() {
_onSuccess?.invoke()
}

fun onSuccess(func: () -> Unit) {
_onSuccess = func
}

override fun onError() {
_onError?.invoke()
}

fun onError(func: () -> Unit) {
_onError = func
}
}

And, an extension function

fun RequestCreator.into(
target: ImageView,
func: __Callback.() -> Unit) {
val callback = __Callback()
callback.func()
into(target, callback)
}

Now, we can write more handsome code.

Picasso.with(context).load(imageUrl).into(imageView) {
onSuccess {
animateImageView()
}
}

I have seen this implementation in Anko library and apply it for other listeners. I called this type of implementation as Optional callback class. If you have another suggestion for naming, please do not hesitate.

I suggest you do not have to use optional callback classes for every listener interfaces. However, if that listener is common and used many times, consider converting to an optional callback class and write handsome codes :)

Kotlin Enthusiast & Android Developer