Why Kotlin?
Before we install anything, it's worth spending a page understanding why this whole track is written in Kotlin rather than Java. Kotlin is a modern, statically-typed language that compiles to JVM bytecode (the same runtime Java targets), which means a Kotlin app runs on Android exactly the way a Java app does — same performance characteristics, same access to the Android SDK, same APK at the end of the day. The difference is entirely in how much code you write to get there, and how many categories of bugs the compiler catches for you before the app ever runs.
Google's Official Position
In May 2019, Google announced that Android development would become Kotlin-first. That didn't mean Java support was dropped — Java still compiles, still runs, and the Android (Java) track on this site is completely valid — but it meant that new Jetpack libraries, official samples, and Google's own documentation would be written with Kotlin as the primary language, with Java treated as the secondary option. Today, most actively maintained Android libraries (Jetpack Compose, Room, Navigation, DataStore, and more) are written in Kotlin first, and some of their nicest APIs — coroutine support in Room, for instance — are only fully idiomatic from Kotlin.
Kotlin vs. Java for Android, at a Glance
| Instead of... | Kotlin gives you... |
|---|---|
| NullPointerExceptions discovered at runtime | Null safety built into the type system — the compiler forces you to handle nullability at compile time |
| Hand-written equals(), hashCode(), toString() | Data classes that generate all three (plus a copy() function) from a one-line declaration |
| Static utility classes (e.g. StringUtils.java) | Extension functions that add new methods directly onto existing classes, including ones you don't own |
| Callback-heavy async code (listeners, AsyncTask) | Coroutines — asynchronous code that reads top-to-bottom like synchronous code, with structured cancellation |
| Verbose anonymous inner classes | Lambda expressions with a lighter, more readable syntax |
Null Safety: A Real Category of Bugs, Gone
In Java, any object reference can be null, and the compiler won't stop you from calling
a method on one — the crash happens at runtime, often far from where the actual mistake was made.
Kotlin splits types into nullable (String?) and non-nullable (String) at
the type level. If a function expects a non-nullable String, the compiler simply won't
let you pass a nullable one without handling the null case first. For an app like TrailLog, where a
Trail's photoUri might legitimately be absent (not every hike has a photo),
this distinction stops "forgot to check for null" bugs before they ever reach a device.
The Same Class, Two Languages
Nothing makes the difference more concrete than writing the same small model class both ways.
Here's a Trail model — just a few fields describing one logged hike — in Java first:
// Trail.java
public class Trail {
private String name;
private String notes;
private double distanceKm;
public Trail(String name, String notes, double distanceKm) {
this.name = name;
this.notes = notes;
this.distanceKm = distanceKm;
}
public String getName() { return name; }
public String getNotes() { return notes; }
public double getDistanceKm() { return distanceKm; }
// ...plus equals(), hashCode(), toString(), all written by hand
}
And here's the same model in Kotlin — a full data model with getters, an equality check, a hash code, and a readable string representation, in one line:
// Trail.kt
data class Trail(
val name: String,
val notes: String,
val distanceKm: Double
)
That's not a simplified example for teaching purposes — that's genuinely the complete, production-ready
equivalent. The Kotlin compiler generates equals(), hashCode(),
toString(), and a copy() function that lets you create a modified copy of a
Trail without mutating the original — all from the data class keyword.
Extension Functions: Adding Behavior Without Inheritance
Kotlin lets you add a new function to a class you didn't write and can't modify — including classes
from the Android SDK or a third-party library. For example, you might want a quick way to format a
Trail's distance for display:
fun Trail.formattedDistance(): String {
return "${distanceKm} km"
}
// usage: trail.formattedDistance()
In Java, the equivalent would be a static method in some TrailUtils class, called as
TrailUtils.formattedDistance(trail) — it works, but it reads backwards and scatters
related helpers into anonymous utility classes. The extension function reads as if the method always
belonged to Trail.
Coroutines: Async Code That Reads Like Sync Code
Loading a list of trails from a database or a network call is inherently asynchronous — you don't
want to block the main thread while it happens. In Java-era Android, this typically meant
AsyncTask, callback interfaces, or manually managed threads with handlers. Kotlin
coroutines let you write asynchronous code that looks sequential:
suspend fun loadTrails(): List<Trail> {
val trails = repository.fetchTrailsFromDatabase()
return trails
}
There's no callback interface, no nested lambda, no manual thread management visible here — the
suspend keyword tells the compiler this function can pause and resume without blocking
a thread. We'll cover coroutines properly in the Kotlin Language Deep Dive section later in this
track; for now, just know it's one of the biggest reasons Kotlin feels different from Java in
day-to-day Android work.
Full Interoperability With Java
Kotlin was designed from day one to be 100% interoperable with Java. In practice this means:
- You can have
.javaand.ktfiles side by side in the same Android Studio project, in the same module, compiling together. - Kotlin code can call any existing Java class or library directly — no wrapper or adapter needed.
- Java code can call Kotlin classes and functions too, with a few small naming conventions
(like
@JvmStaticfor companion object functions) to make the Kotlin code look natural from the Java side.
This matters most if you're maintaining or migrating an existing Java app: you don't need a risky big-bang rewrite. You can convert one file at a time — Android Studio even has a built-in "Convert Java File to Kotlin File" action — while the rest of the app keeps compiling and running throughout the migration.