Today, I want to talk about Kotlin vs Java. Specifically, I’d like to compare the two within the context of Android development.
I frequently run into issues I wish Java could handle directly, so I could spend more time creating apps, and less time wrestling with workarounds. I’ve come to the conclusion that Java is a second-class citizen when compared with other languages, like C# or Scala.
That’s why the Kotlin vs Java debate piqued my interest. I was blown away when I first used Kotlin. It wasn’t just an iterative improvement (solving some issues and creating others) but a close to complete solution to Java’s persistent issues. It’s safe to say I’m now a paid-up member of the Kotlin hype train, jumping on board the moment Google officially supported it. Hopefully, by the time you’ve read through this article, you will be too.
Before you read on though, indulge me for one second. Stop and think about your own experiences with Java and come up with a mental list of 5 things that annoy you the most. Chances are as you read through this you’ll realize that Kotlin has addressed all of them. That’s a real achievement.
My 5 main problems with Java
To get you started, here is my list of things that frustrate me about Java. I’ll get into the specifics of how Kotlin elegantly addresses all 5 in the next section.
1. Data classes. As with any Android app, server communication is crucial, and creating lots of data classes with ‘getters’, ‘setters’, ‘overriding equals’ and ‘hashcode implementations’ is no fun in Java.
2. Async calls. Java has no mechanism for easy ‘async’ execution, except callbacks.
3. Dealing with bad Android SDK APIS. For example, displaying a simple ‘toast’ or canceling a network request when a view is destroyed.
4. Null safety. Every Android developer knows how easy it is to get a ‘null pointer’ exception.
5. Serializing data between Activities is a non-trivial task in Java. There are certain libraries that can make this easier, but nothing compares to the @Parcelize annotation that comes baked into Kotlin android extensions.
Data classes
You’re probably familiar with the concept of ‘data classes’. In simple terms, these are containers for data. And if If you come from a Java background, you’ll know how much code you have to write to get these set up.
Let’s look at an example to better illustrate this. Imagine we are building an app that has two screens, initial authentication and then a home screen with a ‘social feed’ style list of posts. For this, we’d need a container for the ‘User Object’, as the social feed items: Picture
, Video
and PhotoAlbum
.
If we were building this in Java, it would be a fairly involved process:
- First, we’d have to define class fields,
- Then we would need a ‘getter’ and a ‘setter’ for each property,
- Lastly, we would employ ‘equals’ and ‘hashCode’ methods.
A simple data class with few properties would easily reach dozens of lines of code. Kotlin replaces this excess with a data class: a perfect fit for our use case.
data class User(val name: String, val email: String)
data class Picture(val url: String)
data class Video(val url: String)
data class PhotoAlbum(val photos: List)
That’s it, in just 4 data classes and 4 lines of code! We can actually further condense this into just one Kotlin file. When we write a data class in Kotlin, ‘getters’, ‘setters’, ‘equals’ and ‘hashCode’ methods are automatically generated once this code is converted to JVM byte-code. That gives us the power to directly check if two objects are the same without writing the ‘equals’ method ourselves or maintaining it as the class changes. Even the PhotoAlbum
class that contains a list of other objects can be compared using equals, since Photo
is a data class also.
val photoAlbum1 = PhotoAlbum(listOf(Photo("www.google.com/image1")))
val photoAlbum2 = PhotoAlbum(listOf(Photo("www.google.com/image1")))
println(photoAlbum1 == photoAlbum2)
The result of this println()
is ‘true’, because lists compare items by value, not by reference, and we have a data class as an item in the list.
Kotlin vs Java: 1 – 0
Coroutines
Java async execution on Android is not fun to do. You have to juggle managing the UI thread and a network call at the same time. There are two mains ways to do this:
- Callbacks. These aren’t pretty, and they don’t allow a request to be canceled.
- rxJava. If you can get over the steep learning curve, rxJava does enable a request to be canceled but it has no callbacks.
Kotlin’s coroutines help us write async code in a blocking fashion. This way we can just focus on the data rather than how to fetch it. Below is a simple illustration of how Kotlin does just that, fetches data and then displays it as a list:
launch(UI) {
val pair = withContext(DefaultDispatcher, CoroutineStart.DEFAULT) {
val topArticle = service.getTopArticle()
val secondTopArticle = service.getSecondTopArticle()
Pair(topArticle, secondTopArticle)
}
adapterList.clear()
adapterList.addAll(pair.first(), pair.second())
adapter.notifyDataSetChanged()
}
The first block of code executes on the UI thread, and then the withContext()
block returns some data on a separate thread. The great thing here is that adapterList.clear()
will not be called until withContext()
returns a value. This gives us the ability to populate the UI as if we already had the data.
Where coroutines really shine through, are in use cases with multiple calls to the backend. Here we can just group calls inside a withContext()
block and safely use results to populate the UI.
launch(UI) {
val pair = withContext(DefaultDispatcher, CoroutineStart.DEFAULT) {
val topArticle = service.getTopArticle()
val secondTopArticle = service.getSecondTopArticle()
Pair(topArticle, secondTopArticle)
}
adapterList.clear()
adapterList.addAll(pair.first(), pair.second())
adapter.notifyDataSetChanged()
}
In a nutshell, coroutines are lightweight threads. They don’t block the execution of the thread they work in, but they can suspend their own execution.
In the above example:
– Our launch coroutine will be working on the UI thread.
– Then it will start another coroutine withContext()
that will work on the background thread.
– Then the parent (launch) coroutine will suspend its execution without blocking the main thread.
– Then it will wait for the child coroutine to return a result.
– Lastly, we can directly use that result and render it on the screen.
So, no need for callbacks or some external machinery to do networking.
This is a purposefully simplified example, so we did not cover every aspect of coroutines. But rest assured, coroutines can be used in many other use cases including executing network calls in parallel to combine all results and as a tool for periodic or delayed execution.
Kotlin vs Java: 2 – 0
Extension functions
How many utility classes do you have in your Android project? And wouldn’t it be great if, somehow, you could attach those functions directly to objects they work on?
Let me explain. To show a ‘toast’ message in Android we need to specify the context, message, and duration of the toast. But all we typically care about is showing the message itself. So, wouldn’t it be nice if we could somehow just pass a message to a function and magically get our ‘toast’ shown on the screen? Let’s build that magic function.
The Java style is to write a static utility function that can display a ‘toast’. One of the downsides of utility classes is that they have to be directly referenced from all over your code base. This directly couples your code with that utility class and, in turn, goes against SOLID principles of quality software.
What if we could somehow attach a ‘toast’ method, with a better API, directly to the context class? You guessed it: Kotlin to the rescue!
fun Context.toast(message: String, short: Boolean = true) {
val duration = if (short) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
Toast.makeText(this, message, duration).show()
}
To use our ‘toast extension’ function, we can simply write toast(message = “woow”)
in Activities or Fragments. This is much cleaner than a method call on a utility class. What Kotlin does, behind the scenes, is to create a utility function and injects it to the caller’s site without you doing it manually.
Let’s look at a more advanced example, and see how can we leverage the extension functions to cancel network calls when a screen is destroyed. Since Android has no built-in solution for this, we’ll have to build one. We do this by attaching an extension function to a LifeCycleOwner
object and defining at what point we will cancel the network request automatically. (NB: Activities and Fragments are already implemented in this interface)
fun < T > LifecycleOwner.load(work: () - > T): Deferred < T > {
val deferred = async {
work()
}
lifecycle.addObserver(CoroutineLifeCycleListener(deferred))
return deferred
}
infix fun < T > Deferred < T > .then(uiWork: (error: String ? , data : T ? ) - > Unit) {
launch(UI + CoroutineExceptionHandler {
context, throwable - >
throw throwable
}) {
try {
val data = this@ then.await()
uiWork(null, data)
} catch (ex: Exception) {
uiWork("No internet connection or server down", null)
}
}
}
class CoroutineLifeCycleListener(private val job: Job): LifecycleObserver {@
OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
if (job.isCancelled.not()) job.cancel()
}
}
Now, we simply combine the ‘load’ and ‘then’ functions (from above) to have an automatic network canceling mechanism in any Activity, or Fragment, alongside a nice API.
override fun onCreate(){
// other code
load {
presenter.getData()
} then{ data ->
showData(data)
}
}
What’s the best part? We only need to specify our background pool and UI thread in one place, so we can change them later if needed. I saw this technique in a talk by Erik Hellman at a DroidCon conference.
Kotlin vs Java: 3 – 0
Null safety
There is an old conundrum in Java Development: do you place null checks everywhere or do you leave your app susceptible to null pointer exceptions?
Personally, I like to treat all data that comes through a network as nullable, but I also don’t want to put null checks everywhere. That’s because I’m only interested in the cases when data is not null – so I can then show that data.
But how do you treat something as null, and still use it without null checks? You guessed it, Kotlin has some features that will help.
Within Kotlin there is the concept of nullable and non-nullable variables. For non-nullable variables, you have to specify a value immediately upon creating an object (the exception is the late init var
modifier). There is no way to have a null pointer exception and there is no need for null checks. The rule of thumb is this: If you know something may be null (such as data coming from a network call), use nullable variables to protect yourself from null pointer exception.
Let’s see what that looks like:
val firstName: String = “Name”
var lastName: String? = null
Following the example above, it’s safe to write firstName.length
because firstName
is non-nullable. However, lastName
is nullable, and so a null safety operator is required before making any method call on lastName
variable. For example lastName?.length
is okay, while lastName.length
is not. It will not compile.
val description = networkCall.execute().data
description?.let{
descriptionTextView.text = it
}
Let’s look at the code above in more detail. There is the ‘null safety operator’ after the description variable, but a weird ‘let’ keyword after that. Now ‘let’ is a Kotlin extension function which can make an immutable copy of any object. That’s useful because if we combine a ‘null safety operator’ and a ‘let’ function, we can be sure that ‘let’ is called on a non-null object. The ‘let’ function will then make an immutable copy of this object, preventing any modifications from other threads and guaranteeing that we can present that data on the UI
There are some disadvantages to consider when using the safety operator, however. For example, when you have a bug in your app (like the UI is showing empty data), there is a good chance that the safety operator is simply not calling the methods you wanted to call.
Kotlin vs Java: 4 – 0
Data serialization
What if you want to share objects between screens while developing an Android app? The recommended way is to utilize the Parcelable serialization provided in the Android platform, but to do this we would need to write custom code for each individual property to ‘marshal’ and ‘unmarshal’ it. Not to mention maintaining that code as the class changes.
With Kotlin, all it takes is a single annotation – @Parcelize – on the desired class. Better still, if you have a nested structure of objects Kotlin will automatically serialize the whole tree! Since serializing data is cheap and easy, we can utilize this feature to create a better and more consistent user experience. For example, when transferring data from a list screen to a detailed view, it would be nice to show title and description right away while the rest of the data is being fetched.
@Parcelize data class Post(val id:Long, title: String, description: String): Parcelable
intent.putExtra(“post", singlePost)
val post = intent.getParcelableExtra("post")
Even if the Post
object has a nested object that implements the Parcelable interface, it will be serialized without any extra work. This greatly simplifies the process of transferring models between Activities and Fragments, allowing you to focus on better user experience and awesome apps and less on managing and maintaining boilerplate.
Kotlin vs Java: 5 – 0
And the bad news?
By this point you may be apprehensive… nothing this good comes without a downside right?
Well, if pushed, I could come up with two negatives:
- Using Kotlin means you have another dependency to take care of. By that I mean, there are at least three things to keep in sync, Kotlin language version, Kotlin plugin version for Android studio and Kotlin Gradle plugin version.
- Kotlin introduces some cool ideas, like coroutines. But these are experimental and as a result, you may experience an occasional bug.
Kotlin vs Java. The final result.
Kotlin is the clear winner here. While there are some minor steps you will need to take to integrate Kotlin, all things considered, the pros greatly outweigh the cons.
Now, this is just my opinion, who do you guys favor in the Kotlin vs Java debate? I’d love to hear them in the comments. Especially if you are team Java!
We’ve got some talented Kotlin developers at Scalable Path, get in touch with our team to find out how we can help on your next project.