Learn Kotlin through unit tests
At I/O in 2019, we announced that Android is going Kotlin-first. However, some developers have mentioned they’re still uncertain how to proceed. It’s a scary first step to begin writing Kotlin code, especially if no one on the team is familiar with it.
Those of us on the Android Studio profilers team approached this incrementally. The first step was requiring all our unit tests to be written in Kotlin moving forward. Because these classes were isolated from production code, any initial missteps we made would be contained.
During this time I put together the following guide by collecting common issues that our team observed across several code reviews. I am reproducing it below in hopes that it helps others in the broader Android community.
Note: This post is aimed at Kotlin beginners. If you and your team are already writing Kotlin code effectively, this post may not include much new information for you. However, if you do read it and think you would have included something I didn’t, consider leaving a comment!
IDE Action: Convert Java File to Kotlin File
If you’re using Android Studio, the easiest way to start learning Kotlin is to write your test class in Java and then convert it into Kotlin by selecting Code → Convert Java File to Kotlin File
from the menu bar.
This action may ask you “Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?” I recommend selecting “No” so you can focus on just one file at a time.
Although this action generates Kotlin code, what gets produced often leaves room for improvement. The following sections highlight common tips we collected from dozens of code reviews over such auto-generated code. Of course, there is far more to the Kotlin language than what is discussed below, but to keep this guide to the point, it only focuses on the recurring issues we observed.
High Level Language Comparison
Java and Kotlin at a high level look pretty similar. The following is a skeleton test class written in Java and then in Kotlin.
/// Java
public class ExampleTest {
@Test
public void testMethod1() throws Exception {} @Test
public void testMethod2() throws Exception {}
}/// Kotlin
class ExampleTest {
@Test
fun testMethod1() {} @Test
fun testMethod2() {}
}
It’s worth noting what’s streamlined in Kotlin:
- Methods and classes are public by default.
- The
void
return type doesn’t need to be explicitly declared. - There are no checked exceptions.
Semicolons are optional
This is one of those changes that probably feels really uncomfortable at first. In practice, you don’t need to worry about it too much. Just write your code, and if you put in semicolons out of habit, the code will still compile and the IDE will point them out. Just remove them all before submitting.
Hate this choice or not, Java already removes semicolons in some places, which you can notice if you compare with C++ (which requires semicolons in more places).
/// C++
std::thread([this] { this->DoThreadWork(); });/// Java
new Thread(() -> doThreadWork());/// Kotlin
Thread { doThreadWork() }
Types are declared at the end
/// Java
int value = 10;
Entry add(String name, String description)/// Kotlin
var value: Int = 10
fun add(name: String, description: String): Entry
Like optional semicolons, this change is one that you’re likely to find very hard to accept if you’re not used to it. It’s the exact opposite order that many have had ingrained into them over their programming career.
However, the advantage with this syntax is it makes it easier to omit types when they can be inferred. This is touched on further in a later section “Omitting variable types”.
This syntax also puts more emphasis on the variable itself and not its type. I find this order actually sounds more natural when talking about the code out loud:
/// Java
int result; // An integer that is a variable called "result"./// Kotlin
var result: Int // A variable called "result" that is an integer.
The final thing I’d say about this syntax is, as awkward as it is to use at first, you will get used to it over time.
Constructors without “new”
Kotlin does not require the new keyword before a constructor call.
/// Java
... = new SomeClass();/// Kotlin
... = SomeClass()
This may feel at first like you’re losing crucial information, but not really — many functions in Java allocate memory under the hood, and you never cared. Many libraries even have static creation methods, such as the following:
/// Java
Lists.newArrayList();
So, really, Kotlin just makes this more consistent. Any function may or may not allocate memory as a side effect.
This also cleans up the pattern where you allocate a temporary class just to call a function on it without assigning it to anything.
/// Java
new Thread(...).start(); // Awkward but valid/// Kotlin
Thread(...).start()
Mutability and immutability
Variables in Java are mutable by default and require the final
keyword to make them immutable. In contrast, Kotlin does not have the final
keyword. Instead, you need to tag properties with val
to indicate a value (immutable) or var
to indicate a variable (mutable).
/// Java
private final Project project; // Cannot reassign after init
private Module activeModule; // Can reassign after init/// Kotlin
private val project: Project // Cannot reassign after init
private var activeModule: Module // Can reassign after init
Often in Java, you’ll encounter many fields that could have been final
but the keyword was omitted (likely by accident as it is easy to forget). In Kotlin, you must be explicit about this decision on every field you declare. If you’re not sure what it should be, just mark it val
by default and change it to var
later if requirements change.
As an aside, in Java, function parameters are always mutable and, like fields, can be made immutable by using final
. In Kotlin, function parameters are always immutable — that is, they are implicitly tagged as val
.
/// Java
public void log(final String message) { … }/// Kotlin
fun log(message: String) { … } // "message" is immutable
Nullability
Kotlin obsoletes the @NotNull
and @Nullable
annotations. If a value can be null, you need to simply declare its type with a question mark.
/// Java
@Nullable Project project;
@NotNull String title;/// Kotlin
val project: Project?
val title: String
In certain situations, if you are sure a nullable value will always be not-null, you can use the !!
operator to assert that.
/// Kotlin
// 'parse' could return null, but this test case always works
val result = parse("123")!!
// The following line is not necessary. !! already asserts.
❌ assertThat(result).isNotNull()
If you apply !!
incorrectly, it can cause the code to throw a NullPointerException
. Inside a unit test, this just causes a test failure, but you should be extra careful when using this in production code. In fact, many consider !!
in production code a potential code smell (although there’s enough nuance here that this point probably requires its own blog post).
In a unit test, it is more acceptable to assert that a specific case is valid using the !!
operator, because if this assumption ever stops being true, the test will fail and you can fix it.
If you are sure it makes sense to use the !!
operator, then you should apply it as soon as possible. For example, do the following:
/// Kotlin
val result = parse("...")!!
result.doSomething()
result.doSomethingElse()
But don’t do this:
/// Kotlin (auto-generated)
val result = parse("...")
❌ result!!.doSomething()
❌ result!!.doSomethingElse()
Omitting variable types
In Java, you will see lots of code like this:
/// Java
SomeClass instance1 = new SomeClass();
SomeGeneric<List<String>> instance2 = new SomeGeneric<>();
In Kotlin, these type declarations are considered redundant and don’t need to be written twice:
/// Kotlin
val instance1 = SomeClass()
val instance2 = SomeGeneric<List<String>>()
“But wait!” you cry out, “Sometimes I intended to declare those types!” For example:
/// Java
BaseClass instance = new ChildClass(); // e.g. List = new ArrayList
This can be done in Kotlin using the following syntax:
/// Kotlin
val instance: BaseClass = ChildClass()
No checked exceptions
Unlike Java, Kotlin does not require that its methods declare what exceptions they throw. There is no longer a difference between checked exceptions and runtime exceptions.
/// Java
public void readFile() throws IOException { … }/// Kotlin
fun readFile() { … }
However, in order to allow Java to be able to call into Kotlin code, Kotlin does support declaring exceptions indirectly, using the @Throws
annotation. The Java → Kotlin
action plays it safe and always includes this information.
/// Kotlin (auto-converted from Java)
@Throws(Exception::class)
fun testSomethingImportant() { … }
But you never have to worry about your unit tests being called from a Java class. Therefore, you can save some lines and safely remove these noisy exception declarations:
/// Kotlin
fun testSomethingImportant() { … }
Omitting parentheses with lambda calls
In Kotlin, if you want to assign a closure to a variable, you’ll need to declare its type explicitly:
val sumFunc: (Int, Int) -> Int = { x, y -> x + y }
However, if everything can be inferred, this can be shortened to just
{ x, y -> x + y }
For example,
val intList = listOf(1, 2, 3, 4, 5, 6)
val sum = intList.fold(0, { x, y -> x + y })
Note that, in Kotlin, if the last parameter of a function is a lambda call, then you can write the closure outside of the parentheses. The above is identical to:
val sum = intList.fold(0) { x, y -> x + y }
However, just because you can, doesn’t mean you should. Some might say the fold call above looks weird. Other times this syntax can reduce some visual noise, especially if the only parameter to the method was a closure. Let’s say we want to count even numbers. Compare the following:
intList.filter({ x -> x % 2 == 0 }).count()
with
intList.filter { x -> x % 2 == 0 }.count()
Or compare:
Thread({ doThreadWork() })
with
Thread { doThreadWork() }
Regardless of what you think is the best approach, you will see this syntax used in Kotlin code, and it can be auto-generated by the Java → Kotlin
action, so you should make sure you understand what’s going on when you see it.
equals, ==, and ===
Kotlin diverges from Java around equality testing.
In Java, double-equals (==
) is used for instance comparison, which is distinct from the equals
method. While this might sound fine in theory, in practice, it is easy for a developer to accidentally use ==
when they meant to use equals
. This could introduce subtle bugs and take hours to spot or debug.
In Kotlin, ==
is essentially the same thing as equals — the only difference being it also handles the null case correctly. For example, null == x
evaluates correctly, while null.equals(x)
throws an NPE.
If you need instance comparison in Kotlin, you can use triple-equals (===
) instead. This syntax is both more difficult to misuse, and it is easier to spot.
/// Java
Color first = new Color(255, 0, 255);
Color second = new Color(255, 0, 255);
assertThat(first.equals(second)).isTrue();
assertThat(first == second).isFalse();/// Kotlin
val first = Color(255, 0, 255)
val second = Color(255, 0, 255)
assertThat(first.equals(second)).isTrue()
assertThat(first == second).isTrue()
assertThat(first === second).isFalse()
Most of the time you write Kotlin code, you’ll want to use ==
because the need for ===
is relatively rare. As a precaution, the Java to Kotlin converter will always convert ==
to ===
. For readability and intention, you should consider restoring back to ==
when possible. This is common with enum comparisons, for example.
/// Java
if (day == DayOfWeek.MONDAY) { … }/// Kotlin (auto-converted from Java)
❌ if (day === DayOfWeek.MONDAY) { … }/// Kotlin
if (day == DayOfWeek.MONDAY) { … }
Remove field prefixes
In Java, it is common to have a private field paired with a public getter and setter, and many codebases attach a prefix on the field, a vestige of Hungarian notation.
/// Java
private String myName;
// or private String mName;
// or private String _name;
public String getName() { … }
public void setName(String name) { … }
This prefix is meant to be a useful marker, only visible to the implementation of the class, making it easier to distinguish fields local to the class from, say, parameters passed into a function.
In Kotlin, fields and getters/setters are merged into a single concept.
/// Kotlin
class User {
val id: String // represents field and getter
var name: String // represents field, getter, and setter
}
However, when you auto convert code, the Java prefix sometimes gets carried over, and what was once a detail hidden inside the class can leak into its public interface.
/// Kotlin (auto-converted from Java)
class User {
❌ val myId: String
❌ var myName: String
}
In order to prevent prefixes from leaking, it is recommended to get in the habit of removing them for consistency.
Fields without prefixes may make the occasional code review using web tools a bit harder to read (for example, in a function that’s too long in a class that’s too large). However, when you read the code inside an IDE, it’s clear which values are fields and which are parameters by their syntax highlighting. Removing prefixes may also encourage better coding habits around writing more focused methods and classes.
Closing Thoughts
Hopefully, this guide helps kickstart your Kotlin learning. First, you’ll start by writing Java and converting it to Kotlin, then you’ll write Java-like Kotlin, and finally, before too long, you’ll be writing idiomatic Kotlin code like a pro!
This post just scratches the surface of what you can do with Kotlin. It aims to be a minimal set of notes for those who don’t have a lot of time and just need to get their first Kotlin test up and running quickly, while still introducing them to a fair amount of the language’s foundation.
However, it likely won’t cover everything you will need. For that, consider the official documentation:
Language Reference: https://kotlinlang.org/docs/reference/
Interactive Tutorials: https://try.kotlinlang.org/
The language reference is very useful. It covers all topics in Kotlin without going so deep as to be overwhelming.
The tutorials will give you an opportunity to practice using the language, and they also include koans (a series of short exercises) to help confirm your newly acquired knowledge.
And finally, check out our official codelab for refactoring to Kotlin. It covers topics introduced in this blog post and much, much more.