What is Jetpack Compose?

Before writing a single composable, it's worth slowing down on the one idea that makes Compose feel different from every UI toolkit you may have used before: the shift from imperative to declarative UI. This isn't a syntax difference — it's a different way of thinking about what your UI code is even responsible for. Get this mental model right and the rest of the track (state, recomposition, side effects) will click into place much faster.

Imperative UI: Describing the Steps to Change Things

With traditional Android Views (XML layouts + findViewById), your code is a set of instructions for mutating a live view tree over time. You find a widget, and you tell it, step by step, how to change:

// Imperative: you mutate a specific TextView instance by hand
val titleView = findViewById<TextView>(R.id.recipeTitle)
titleView.text = recipe.title
if (recipe.isFavorite) {
    titleView.setTextColor(Color.RED)
} else {
    titleView.setTextColor(Color.BLACK)
}

Notice what this code actually says: "go find this exact TextView, then change its text, then check a condition and change its color accordingly." If recipe.isFavorite changes somewhere else later, nothing happens automatically — some other piece of code has to remember to re-run this same mutation logic again. As an app grows, keeping every possible mutation path in sync with the current data becomes the single biggest source of Android UI bugs: a stale label, a checkbox that doesn't reflect the real state, a color that's one step behind.

Declarative UI: Describing What the UI Looks Like For a Given State

Compose flips this around. Instead of writing steps that mutate a persistent view tree, you write a function that describes what the UI should look like right now, given the current data. There is no findViewById, no widget instance you reach into and change — you just re-describe the whole thing, and Compose is responsible for figuring out what actually needs to be redrawn on screen.

@Composable
fun RecipeTitle(recipe: Recipe) {
    // Declarative: describe the UI for THIS recipe, don't mutate anything
    Text(
        text = recipe.title,
        color = if (recipe.isFavorite) Color.Red else Color.Black
    )
}

This RecipeTitle function doesn't know or care whether it's being drawn for the first time or the hundredth time. Every time it runs, it simply states the correct output for the recipe it was given. When recipe.isFavorite changes, Compose re-invokes this function (a process called recomposition, covered in detail a few lessons from now) and the new description — red or black — is what ends up on screen. You never wrote code that says "now go update the color"; you only ever described the correct end state.

Imperative (Views)Declarative (Compose)
"Find this view, then change this one property""Here's what the UI looks like for this data"
UI and mutation logic can drift out of syncUI is always a direct function of current state
You track view identity (a reference to one specific instance)You track state, not view instances
Updating means writing new mutation code for every pathUpdating means changing the state; the function re-runs itself

Compose is a Kotlin DSL, Not a New Language

It's easy to look at code like RecipeTitle above and assume Compose invented some new templating language. It didn't — every composable is a completely ordinary Kotlin function, and Text(...), Column { }, and every other composable you'll meet are just regular function calls. The only thing marking a function as special is the @Composable annotation, which tells the Kotlin compiler plugin that ships with Compose to instrument this function so it can participate in recomposition. There's no separate templating syntax to learn, no XML, no string-based binding expressions — it's if, for, local vals, and ordinary function calls, exactly like any other Kotlin code you already know from the Android (Kotlin) track.

Note: Because composables are plain Kotlin functions, you get real language features for free that XML never had — loops to generate a list of items, if/when to branch UI, and full IDE support (autocomplete, refactoring, type-checking) on your UI code itself.

Where Compose Fits vs Traditional XML Views

Compose is Google's recommended toolkit for new Android UI going forward, but it is not a hard replacement that every existing app must adopt overnight. Both approaches ultimately draw to the same underlying Android View/Canvas system under the hood — Compose just gives you a fundamentally different, higher-level way to describe that output. Throughout the rest of this track we'll build RecipeBox — the same recipe-manager app used across the site's Android (Java), Android (Kotlin), and Testing tracks, with its Login, Recipe List, Add/Edit Recipe, and Search screens — entirely with Compose, so you'll see the exact same product requirements expressed in a completely different UI paradigm.

Honest note on interop: A single app is allowed to mix both worlds — a Compose screen can host a traditional View, and a traditional Fragment/Activity can host a Compose screen via ComposeView. This is genuinely common during a gradual migration, and it's covered properly later in this track in Interop with the View System. For now, everything we build will be pure Compose so the mental model stays clean.