90 Days of Kotlin

Adetunji Dahunsi

Riding the wave of change

Old habits die hard. We’re creatures of habit, and provided there’s no stimuli that causes us to change and evolve, we’ll all very likely stay static. After all the more you repeat something, the better you get at it, and there’s little to be gained from change for change’s sake save for a new perspective, which can be rather varied in its returns.

I’ve been an Android Developer for a little over 5 years now, and I love Java. Quite a bit actually; its APIs for common data structures, its explicitness and the way it makes it easy to read someone else’s code, its OOO approach and how it lends to easy encapsulation and delegation to class instances, its recent adoption of a pseudo functional paradigm with functional interfaces and single abstract methods… the list goes on. Not only does it offer all this, but it does so while being backwards compatible with previous versions of the language. It’s therefore not surprising Java and the JVM form the thriving ecosystem they are today. I’ve spent a considerable amount of time learning about the language and its design decisions and they all seem extremely justifiable to me.

However, the programming world moves fast, and with Google embracing Kotlin more and more each year, I needed not only to switch, but to be in an environment that I could use Kotlin in, day in and out. Here at Gamechanger, I’ve found that, and have been learning and using Kotlin for the past 90 days, and there’s quite a lot to like.

Fundamentally Kotlin doesn’t try to re-invent the wheel, nor is it some great departure from Java that would cause an epic schism or anything of the sort in the JVM ecosystem. If anything, it readily embraces its heritage with 100% interoperability with Java. What Kotlin seeks to do instead, is get out of your way, and let you do as much as you already could with a lot less effort and code.

Compiler niceties

A lot of what Kotlin does I’ve found, is to take things the compiler could do, and simply make the compiler do them. From inferring generic types, to smart casting and much more. Take for example casting an object:

In Java:

	Object item = container.getItem();

	if(item instanceof Car) ((Car) item).drive();
	if(item instanceof Plane) ((Plane) item).fly();
	if(item instanceof Rocket) ((Rocket) item).launch();

and the Kotlin equivalent:

	when(val item = container.item) {
		is Car -> item.drive()
		is Plane -> item.fly()
		is Rocket -> item.launch()
	}

In Java, the cast is still necessary despite having just performed an instance check right before. In Kotlin, the compiler is smart enough to skip the ceremony and let you start interacting with the Item as what you’ve already ascertained it is, a Car.

The standard library

In Java, creating a list, or map of items is a bit of a ritual. The tersest form for creating a mutable list of names is:

    List<String> names = new ArrayList<>(Arrays.asList("Jack", "John", "Mary"));

And even that is shrouded in nuance, the wrapping ArrayList is only necessary to make the List mutable, as the Arrays.asList method returns an unmodifiable List. Trying to mutate it would cause an exception and you wouldn’t know unless you read the docs beforehand, or at runtime.

Kotlin instead uses the more readable

    val names = mutableListOf("Jack", "John", "Mary")

Which is less verbose, and yet much clearer. Kotlin actually separates mutable collection types from immutable ones, there are no methods available to mutate a List, they only exist on a MutableList. Whereas prior you couldn’t communicate that a List was unmodifiable without throwing an Exception, you can now easily convey it in the language itself.

Infix Functions

Another amazing example of how the language gets out of your way to let you get things done are infix functions. Similar to the above, let’s create a map of numbers to names in Java.

	Map<Integer, String> map = new HashMap<>();
	map.put(1, "Jack");
	map.put(2, "John");
	map.put(3, "Mary");

And in Kotlin:

    val map = mutableMapOf(  
      1 to "Jack",  
      2 to "John",  
      3 to "Mary"  
    )

Again Kotlin is more succint, and more than that, the mapping operator to is not a language keyword, it’s a regular function that creates a Pair of two elements, i.e:

    val pair = 1 to "Jack"

is equivalent to

    val pair = 1.to("Jack")

is equivalent to

    val pair = Pair(1, "Jack")

By declaring a function as infix, you can write more readable code with less ceremony, provided you meet the requirements of the infix notation. You can even write your own infix functions if you so desired, and can call them with the same syntax.

Extension Functions and Receivers

Finally, my favorite Kotlin ability, are extension methods and receivers. Extension methods are statically resolved syntactic sugar that let you define methods on a class, as though you owned it without having to inherit from it. This is very common in Android where framework classes are build for the lowest common denominator and have fairly open APIs for you to make them as robust as you wish.

Take for example, trying to get the current displayed View in a ViewPager. In Java, a utility method would need to be created like this:

    public static View getCurrentView(ViewPager viewPager) {
	    ...
    }

While in Kotlin:

    val ViewPager.currentView: View?
	    get() {
		    ...
	    }

Calling the method in Java would take the form:

    View currentView = getCurrentView(viewPager)

and Kotlin

    val currentView = viewPager.currentView

Of the two, the Kotlin version reads better, as for all intents and purposes, the current view in a ViewPager really is a property of the ViewPager itself despite it not being included with the ViewPager dependency.

This is but a tip of the iceberg however. We’ve been able to add a property to a class we have no control over, but what if you could execute arbitrary code in the context of any class at anytime?

Think of functional interfaces in Java like a Consumer<T> but instead of the consumer receiving the instance of the object T, it also receives the execution context of T, as though you were writing code inside the body of class T.

To illustrate, let’s bring back the Container class from earlier. Assume the class has since been refactored to contain generic bounds for the item contained within, so a Container of a Car now has the signature Container<Car>.

This container is passed around quite often, so in Java I could define a method called onItem of the signature:

    void onItem(Consumer<T> consumer) {
    	consumer.apply(item)
    }

And if I were to pass this to a mechanic for example, the mechanic could say,

    container.onItem(car -> {
    	int fuelLevel = car.fuelLevel();
    	if(fuelLevel < 3) car.stopEngine();
    });

Which is great and rather succinct. However in Kotlin, rather than having to pass the car, you could pass the context of the car like this.

    fun onItem(receiver: T.() -> Unit) {
    	receiver.invoke(item)
    }

Then use it like

    container.onItem {
    	if(fuelLevel < 3) stopEngine();
    });

Within the scope of the lambda expression, I’m writing code as though it were inside the body of the Car class. All the instance variables and methods in the Car class are available in the lambda context, as well as those available in the execution context of the method block the receiver was called in. This avoids having to create temporary variables for state within the car class that we may need, bloating our code base as it’s readily accessible within the Car context.

The Kotlin Standard library has various permutations of different kinds of methods that use receivers. They take some getting used to, but really are quite lovely, especially when working with Nullable types.

Wrap up

Kotlin has a lot more to offer than just these perks, but in my 90 days, these are the ones I’ve enjoyed the most as they dealt with the parts of Java I wasn’t fond of.

Do I still love Java? Yes, but more in a legacy way now. Without it, there’d be no Kotlin, but Kotlin improves on in it so many ways that it really is night and day between the two. It’d also be non trivial to bring those same Kotlin features to Java in a backwards compatible way. It only took 90 days, but I wholly prefer Kotlin to Java. Couldn’t be a second earlier either, Google IO 2019 was a couple months ago… unsurprisingly, Google is going Kotlin first with Android now, I’m just glad I caught the train.