Kotlin Language Basics

With Android Studio installed and TrailLog created, it's time to actually learn the Kotlin language. This page covers the fundamentals you'll use in nearly every file you write for the rest of this track: how variables are declared, the basic built-in types, and the three control-flow constructs (if, when, and for) you'll reach for constantly.

val vs. var

Kotlin has two keywords for declaring a variable, and the choice between them is a deliberate design decision baked into the language, not just a style preference:

  • val declares a read-only reference — once assigned, it can't be reassigned to point at something else (roughly equivalent to a final variable in Java).
  • var declares a mutable reference — it can be reassigned as many times as you like.
val trailName = "Blue Ridge Loop"   // cannot be reassigned
var distanceLogged = 0.0          // can change as more distance is added

distanceLogged = distanceLogged + 4.2   // fine, distanceLogged is a var
// trailName = "Something Else"   would NOT compile — trailName is a val
Why Kotlin encourages val by default: In Java, a plain local variable declaration is mutable unless you explicitly add final — so most Java code defaults to mutability without really deciding to. Kotlin flips that default: reach for val first, and only switch to var when you have an actual, deliberate reason for a value to change. Code built mostly from vals is easier to reason about, because you can trust that a value you read early in a function is still the same value later in that function — nothing snuck in and changed it.

Type Inference

Kotlin is statically typed — every value has a type, checked at compile time — but you rarely need to write the type out yourself. The compiler infers it from the value on the right-hand side:

val name = "TrailLog"          // inferred as String
val trailCount = 12            // inferred as Int
val isMetric = true            // inferred as Boolean

// you CAN write the type explicitly if you want to be clear, or if there's no
// initial value to infer from yet
val maxDistanceKm: Double = 42.0

Writing the type explicitly is never wrong, and it's often clearer in function signatures (as you'll see in the next lesson) — but for local variables where the value makes the type obvious, most Kotlin code leans on inference.

Basic Types and String Templates

The everyday built-in types you'll use throughout TrailLog are Int, Double, Boolean, and String. One feature worth calling out on its own is string templates — the ability to embed expressions directly inside a string literal using $:

val name = "TrailLog"
val greeting = "Hello, $name"              // "Hello, TrailLog"

val distanceKm = 7.5
val summary = "You covered ${distanceKm} km"  // "You covered 7.5 km"

// any expression works inside ${ }, not just a variable name
val label = "${distanceKm * 2} km round trip"

This is genuinely one of Kotlin's most-used conveniences day to day. In Java, building the same string means concatenating with + or calling String.format(); in Kotlin, you just write the expression inline where it belongs. For a single variable, the curly braces are optional ("$name" works fine) — but any expression more complex than a bare variable name needs the ${...} form.

if as an Expression

In Java, if is purely a statement — it controls which block of code runs, but it doesn't itself produce a value. In Kotlin, if is an expression: it evaluates to a value you can assign directly.

val distanceKm = 9.5

// if as an expression — no separate variable declaration needed beforehand
val difficulty = if (distanceKm < 5.0) {
    "Easy"
} else if (distanceKm < 15.0) {
    "Moderate"
} else {
    "Hard"
}
// difficulty is now "Moderate"

The last expression in each branch is automatically the branch's "return value." This removes an entire category of Java code where you declare a variable, then reassign it inside an if / else if / else chain — in Kotlin, the assignment and the branching happen in one expression.

when: A More Powerful switch

Kotlin's when replaces Java's switch, but it's considerably more capable — it can match ranges, types, and arbitrary boolean conditions, not just exact constant values, and like if, it can be used as an expression.

fun colorForDifficulty(difficulty: String): String {
    return when (difficulty) {
        "Easy" -> "green"
        "Moderate" -> "orange"
        "Hard" -> "red"
        else -> "gray"  // "else" is required when when() is used as an expression,
                          // unless every possible case is already covered
    }
}

// usage
val badgeColor = colorForDifficulty("Moderate")  // "orange"

This example maps a Trail's difficulty level directly to a display color for a badge in the UI — exactly the kind of small mapping function you'll write constantly in Android code. Unlike Java's switch, there's no break needed between branches, and no silent fall-through bugs from a forgotten break.

for Loops: Ranges and Collections

Kotlin's for loop always iterates over something iterable — a range of numbers or a collection — rather than the C-style three-part loop from Java.

// looping over a range of numbers (1 through 5, inclusive)
for (i in 1..5) {
    println("Day $i of the hiking challenge")
}

// looping over a collection of trail names
val trailNames = listOf("Blue Ridge Loop", "Cedar Falls Trail", "Summit Pass")
for (name in trailNames) {
    println("Logged trail: $name")
}

The 1..5 syntax creates a Range — a lightweight, iterable sequence of integers. We'll cover collections (lists, sets, maps) properly later in this track, in the Kotlin Language Deep Dive section; for now, just know that iterating over one with for looks exactly like iterating over a range.

Putting It Together

Here's a small function that combines several of the pieces from this page — val, string templates, if as an expression, and a for loop — into one realistic snippet, describing a batch of logged trails:

fun describeTrails(distances: List<Double>) {
    for (distanceKm in distances) {
        val difficulty = if (distanceKm < 5.0) "Easy" else "Hard"
        println("A $distanceKm km trail is rated: $difficulty")
    }
}
Next up: Continue to Functions & Classes, where we turn these basics into reusable functions and start modeling TrailLog's Trail class properly.