Kotlin is a relatively new language for the Java Virtual Machine that makes Android development nicer and streamlined in many ways. We’ve begun to use Kotlin in many of our Android apps and have found many advantages both in development time and quality. Kotlin is a language that features built-in null checking, extension methods for adding functionality to existing classes, and it is fully interoperable with Java code. Below are only a few of my favorite features of Kotlin that are an improvement upon Android development in Java.
Nullability
In Java, all objects are nullable and can be assigned to null
and methods can
return null
. Without rigorous null-checks, this can lead to
NullPointerException
s and can necessitate long annoying chains of null-checks
to safely look deep into an object.
Order order = getOrderOrMaybeNull();
String orderCity = null;
if (order != null) {
if (order.getCustomer() != null) {
if (order.getCustomer().getAddress() != null) {
orderCity = order.getCustomer().getAddress().getCity();
}
}
}
All this null-checking cruft can make it tempting to leave these null checks out and make the dangerous assumption that “these will almost definitely (probably) never be null”.
Kotlin denotes nullable objects and operators with ?
marks. A variable is
declared to be nullable by appending a question mark to its type.
// non-null type, can never be null, and must be given a value when defined
var someObject: SomeObject = SomeObject()
// nullable type, can be null, denoted by the ? mark
var someObject: SomeObject? = null
Accessing methods and fields in a nullable object necessitates the use of the
safe call operator (?.
). This allows us to express the
above deep-look into an object in a far less verbose way.
var orderCity = orderOrMaybeNull?.customer?.address?.city
Much more concise, readable, and the null-safety is enforced by the language.
Should any of the objects in the chain be null
, orderCity
will evaluate to
null
, with no NullPointerException
s being thrown.
You’ll also notice that Kotlin does away with the verbosity of getter and setter
methods and lets you access class properties by their name, with optional
getter and setter methods. A Kotlin class will still provide access to these
properties through getSomeObject()
methods when being referenced from Java
code.
Nullability annotations
Java types are treated by Kotlin as not strictly null or non-null and are
exempt from Kotlin’s nullability enforcement. This means that if a Java type is
treated by Kotlin code as non-null but ends up being null, then a
NullPointerException
will happen. However, the Kotlin compiler and
Kotlin-supported IDEs can recognize nullability
annotations that indicate if a method has arguments
and a return type that are nullable or not.
Here’s an example of using the Android support library nullability annotations in Java code.
import android.support.annotation.Nullable;
public class Utils {
@Nullable
public static String getBooleanAsString(@Nullable Boolean bool) {
if (bool == null) {
return null;
}
if (bool) {
return "true";
} else {
return "false";
}
}
...
}
When this method is called from Kotlin code, both the argument and return type are treated as nullable types.
// this is fine
Utils.getBooleanAsString(true)?.capitalize()
// this is not
Utils.getBooleanAsString(true).capitalize()
For code that simply can’t be converted to Kotlin yet, this is not only a great
way to indicate the intent of method arguments and return types, but also
enforce them in Kotlin code. Your IDE may also show warnings in your Java code
if it thinks you might pass or return a null object to a @NonNull
annotated
method.
Extension methods
Do you ever find yourself writing static utility class methods to add needed functionality to a class? Of course you do.
Here’s one we use for reconciling the fact that
ViewTreeObserver.removeGlobalOnLayoutListener()
was deprecated and renamed to
ViewTreeObserver.removeOnGlobalLayoutListener()
in Android SDK v16 (Jelly
Bean).
import android.os.Build;
import android.view.View;
import android.view.ViewTreeObserver;
public class ViewUtils {
public static void removeOnGlobalLayoutListener(View view, ViewTreeObserver.OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//noinspection deprecation
view.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
view.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
...
}
The call to this looks like
ViewUtils.removeOnGlobalLayoutListener(view, listener);
Straight-forward enough, but wouldn't it be so much nicer if we could just add
this reconciliatory method right to the View
class itself? With Kotlin
extension methods, now you can (in a sense).
import android.os.Build
import android.view.View
import android.view.ViewTreeObserver
fun View.removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
@Suppress("DEPRECATION")
this.viewTreeObserver.removeGlobalOnLayoutListener(listener)
} else {
this.viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
}
This tells the compiler that, syntactically, we want this method to be treated
as though it's part of the View
class that we can't modify. With an import
pointing to the extension method, we can now call this method like it was part
of the View
class itself. Notice also that Kotlin lets you define
free-standing methods like this outside of a class.
import com.michiganlabs.app.extensions.removeOnGlobalLayoutListener
...
view.removeOnGlobalLayoutListener(listener)
Interoperability with Java
Kotlin's interoperability with Java is what makes a transition to using Kotlin
in existing Android Java projects very smooth and relatively painless. We can
build both Java and Kotlin code files in the same project, and with only a few
additions to the project's Gradle build configuration file. Kotlin compiles down
to Java byte code, so in the end, Kotlin is essentially syntactical sugar on top
of the JVM.
From Kotlin code, we can call Java code with almost no issues. From Java code,
we can also call Kotlin code with occasional modifications, such as with the
extension method in the above example. The Kotlin compiler generates a ViewKt
class for us, with a static method that takes a `View` as its first argument in
identical fashion to the static Java utility method we had earlier.
import com.michiganlabs.app.extensions.ViewKt;
...
ViewKt.removeOnGlobalLayoutListener(view, listener);
Going forward
Kotlin has already proven to be a significant improvement for us in Android development, from a decrease in bugs to the increased readability of code and the reduced development time. Delivering quality software in a timely manner is always a challenge, but with the many niceties of the Kotlin language, it's become just a bit easier and more efficient.
Looking for more like this?
Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.
![MichiganLabs’ approach to software delivery: 3 ways delivery leads provide value](https://gratis-creeper.transforms.svdcdn.com/staging/blog/approach-to-software-delivery-thumbnail.png?w=1200&h=1200&auto=compress%2Cformat&fit=crop&dm=1708017427&s=85c458fd9ad34a859f3e71cb50843aed)
MichiganLabs’ approach to software delivery: 3 ways delivery leads provide value
February 12, 2024Delivery leads ensure the successful execution of custom software development. They build great teams, provide excellent service to clients, and help MichiganLabs grow. Learn what you can expect when working with us!
Read more![UX Writing Tips](https://gratis-creeper.transforms.svdcdn.com/staging/blog/UX-Writing-Tips.jpg?w=696&h=320&q=75&auto=format&fit=crop&dm=1708017708&s=83114e23adbaa2c02b8fed21ebb236e7)
UX Writing Tips
February 3, 2023Kai shares a few tips he's collected on how to write for user interfaces.
Read more![How to approach legacy API development](https://gratis-creeper.transforms.svdcdn.com/staging/blog/legacy-api-thumbnail.jpg?w=1080&h=1080&auto=compress%2Cformat&fit=crop&dm=1712166544&s=336e5f9a61c0c6ec1d3ff90cc58ea1c9)
How to approach legacy API development
April 3, 2024Legacy APIs are complex, often incompatible, and challenging to maintain. MichiganLabs’ digital product consultants and developers share lessons learned for approaching legacy API development.
Read more