Navigating the land of Android development can be daunting, especially with the framework changing drastically in the last five years. From the more restrictive ecosystem (Permissions, Services), the introduction of Android Jetpack Components, to the Android development team finally doubling-down on a recommended application architecture (MVVM), none can compare to the most drastic paradigm shift in the development experience on the Android framework: Kotlin.
It’s up to us as developers to reroute our thinking and provide a better path for future developers especially when we run into confusing terrain.
Kotlin is currently seeing rapid growth in adoption within the Android development community. For the uninitiated, the first thing to know about Kotlin is that it is a language developed by JetBrains. The second thing is that it is completely interoperable with Java and acts as a flexible and powerful language that strips away some of the boilerplate we’re used to when developing in Java. I’ve seen many developers shy away from wanting to learn Kotlin, either because they view it as syntactic sugar that solves the same problems that Java does or they find the syntax to be too confusing due to their predisposition to being a veteran Android developer, in which case, the constructs of the language may be too drastically different than what they are used to.
Syntactic sugar and the fear of a new language.
Syntactic sugar is syntax designed to make things easier. Couldn’t one argue then that all languages are syntactic sugar over another? As developers, we are inherently lazy (some more than others 😉), and we want to find new and quicker ways to do the same thing. If for any reason that new thing does come along, isn’t it worth it to give it a shot instead of brushing it off as just syntactic sugar?
As for the latter, I, too, have felt the same way. I was initially hesitant about using the language, having developed on Android in Java for five years. After being pushed into the deep end of a haphazardly designed Kotlin application, I also felt like Kotlin had already left a sour taste in my mouth. Perhaps because of work, but maybe for personal interests and growth as a developer, I decided to take a step back. After I was able to differentiate the various ways one can develop in Kotlin, using it for Android development has been a breeze.
Now, there is a big difference here between can and should; there are ways that Kotlin can be used, and there are ways that it should be used. If used correctly, from what I’ve seen, Kotlin can be one of the most powerful coding languages out there. But if used incorrectly, it can be a daunting challenge for both the person writing the initial code and for anyone who comes along after the fact. Due to the flexibility and conciseness of the language, it provides us with many avenues when tackling a problem and this could give rise to a number of solutions. Sometimes the avenues we take aren’t the best ones, but it’s up to us as developers to reroute our thinking and provide a better path for future developers especially when we run into confusing terrain.
Some real examples of misused code.
I recently worked on a project where I joined after the initial code was written. I was tasked with updating various aspects of the code which was written in Kotlin by a previous developer. I share here a few examples from my experience where Kotlin has been, in my humble opinion, “misused”. In any other case, I imagine that such examples of misused code could stop a developer at a glance from even considering its usage.
Extension Functions
Extensions functions give you the ability to extend a class you don’t control, allowing you to add methods to it. If you’re doing an operation on a class, for example, Date
, consider using an extension function isToday
instead of adding a util method.
Now, let’s look at what you probably shouldn’t do with extension functions.
Bad
Now let’s see it in action.
Better
When I saw this block of code, I couldn’t help but think that I’ve seen this logic before. It comes in the form of the most basic constructs we’ve learned as developers: if and else. I could never really pinpoint the rationale for using the above code (maybe the developer wanted to stand out from the crowd, or maybe they did it because they could). The powerful thing about Kotlin is that it allows us to extend established types with more functionality, but the downside is that it can’t enforce what can and can’t be extended. Because of that, we get the code above. From a readability standpoint, the code puts extra cognitive load on an incoming developer as they try to parse the usage of these functions. Questions I asked myself,
- What does then and otherwise mean?
- Why not use the normal code blocks we are used to?
- Is this the standard for conditional logic within the application codebase (unfortunately, it was only used in one place 🤦)?
Another example where extensions could be misused is when extending a type that shouldn’t have anything to do with what the extension does.
Bad
What’s particularly wrong about this usage is that I can now call the extension function on practically any String in the given context. Something like "blah" header { ... }
is completely allowed in this case; however, the most glaring problem is that a String should have nothing to do with specifying a header. Especially in this example, the String modifies a View that is declared in an Activity based on the value of the String. In my opinion, an extension function’s implementation should only be modifying the type that is being extended, so a String really shouldn’t have any kind of implicit dependency on any other type (in this case, an Activity). Again, this puts a lot of cognitive load on the reader as there are multiple types at play in the extension function and the, albeit shorter, piece of code is arguably harder to parse as it hides a bit too much information about what’s going on.
Better
Global Variables/Functions
Kotlin allows us to declare as many global functions and variables and place them anywhere within the application structure. Think of global functions and variables as static functions and variables, à la Java, that don’t necessitate creating a class to contain them.
Bad
The snippet above was declared all in one file with no enclosing class. This also means that the referenced variables inside the function also have to be declared globally or passed in as parameters. This could lead to unexpected results, especially if the referenced variable is modified somewhere else, which is entirely possible as the variables are globally accessible.
Another downside here is that the function can be easily confused, as there’s the possibility for more than one function of the same name. In this case, when calling the function, it simply appears as publish(...)
with no reference to the source of the declaration. Of course, one could argue that you could just check that by going to the declaration or looking at the
Better
Infix Functions
Kotlin is a very powerful language, and it gives you access to a large toolset to help you write code exactly as you want. infix
is a very powerful keyword, but with great power comes great responsibility. In essence, infix
will allow you to replace the dot when calling a method with a space, and skip the parenthesis.
For example, myObject.doSomething(10)
becomes myObject doSomething 10
.
Now, you may ask yourself, why would you want that? And to be honest, it really depends on exactly what you’re trying to write. One good example is that with infix
, you now can write your own DSL (Domain Specific Language) easily. Where infix
can become a nightmare is when it’s used everywhere in an attempt to make things feel more modern.
Say we’re looping through a list, printing out each element, and we want to spice it up with infix
.
Better
You discover this snippet of code floating around in some of your classes. You decide to look at what iterate
does.
Bad
In this example, iterate
is just calling a forEach
with the fancy infix
syntax. In comparison to the initial way, you can see how adding infix
complicates the code. Not only are we adding more cognitive load to the developer (“What does iterate do? How is it different than a forEach
?”), we’re also adding more lines of code to the project with these small extension functions.
Keep it simple.
I wanted to share the above examples to illustrate the difference between what I perceive to be good code and bad code. It isn’t to say that the examples above should never be used in practice, but it’s important to take away that as developers we should always try to keep things as simple and readable as possible. Doing so increases the information one can convey and reduces the amount of head-scratching another developer would have to do when they inevitably encounter the code.
Kotlin is a great language and it isn’t going away anytime soon. As of Google I/O 2019, it was announced that Android development will become increasingly Kotlin-first and that there would be a continued investment in tooling, documentation, and training to make Kotlin even more ubiquitous. More than ever, there’s no better time to get acquainted with the language and build best practices for using it. Specifically for developers already using Kotlin, I encourage you to take care in writing cleaner Kotlin for newcomers in order to proliferate its usage and showcase its vast capabilities.
*Note that the examples in this article have been edited to protect clients and the author.