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 sync | UI 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 path | Updating 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.
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.
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.