Skip to content
intermediate Phase 4 · Jetpack Compose

Compose Introduction

Understand declarative UI, composable functions, and how Compose differs from XML views.

40m
0 problems
Topic Progress 0%

Imperative vs Declarative UI

The Problem with XML Layouts

With traditional Android development, you define UI in XML and imperatively mutate it from Kotlin/Java code. This creates a split between what the UI looks like (XML) and how it changes (code). As screens grow complex, synchronizing these two worlds becomes error-prone.

<TextView
    android:id="@+id/greeting"
    android:text="Hello"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
findViewById<TextView>(R.id.greeting).text = "Hello, $name"

Every time state changes, you manually find views and update them. Forgetting one update causes bugs that are hard to trace.

The Declarative Alternative

Jetpack Compose flips this model. You write one function that describes what the UI should look like given a set of inputs. When inputs change, Compose automatically re-runs the function and updates the screen.

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name")
}

There is no XML. There is no findViewById. You describe the desired state, and Compose figures out the diff.

How Compose Works Under the Hood

Compose uses a compiler plugin that transforms your @Composable functions into instructions for a slot table -- a data structure that tracks what UI nodes exist and where. When state changes, Compose marks affected composables as invalid and re-executes only those functions. The runtime compares the new slot table against the previous one and issues targeted updates to the Android View system.

This process is called recomposition. It is not the same as rebuilding the entire view tree -- Compose is smart enough to skip recomposition of composables whose inputs have not changed.

Side-by-Side Comparison

Aspect XML Layouts Compose
UI Definition XML files Kotlin functions
State Updates Manual setText, setVisibility Automatic via state changes
Preview Layout Editor @Preview annotation
Lists RecyclerView + ViewHolder LazyColumn built-in
Navigation NavController + XML graphs NavHost composable
Testing Espresso Compose testing (semantics)

When to Use Compose

Compose is the recommended approach for new Android projects. Google has committed to Compose as the future of Android UI. You will still encounter XML in legacy codebases. The two can coexist via ComposeView and AndroidView.

The Compose Runtime and Recomposition

What Happens When You Call a Composable

When setContent is called, Compose builds the initial composition by executing your composable functions. Each function call produces UI nodes in the slot table. This first pass is called composition.

After composition, the UI is rendered. When something changes -- a button tap, a data fetch completing -- Compose identifies which composables read that changed value and re-executes only those. This is recomposition.

Recomposition Is Not Rebuild

Recomposition does not re-execute every composable in the tree. The compiler performs several optimizations:

  • Smart skipping: Composables whose parameters have not changed are skipped.
  • Restartable points: Each composable is a restartable point, allowing fine-grained recomposition.
  • Inline lambdas: Layout lambdas are inlined, avoiding allocation overhead.
@Composable
fun UserProfile(name: String, avatar: ImageBitmap) {
    Column {
        Image(bitmap = avatar, contentDescription = null)
        Text(text = name)
    }
}

Stability and Skippability

For Compose to skip recomposition, parameters must be stable. A stable type is one whose composition is guaranteed not to change between recompositions. Primitive types, String, and Kotlin data classes with stable properties are stable. Classes that hold var properties or mutable collections are not.

// Stable - data class with val properties
data class User(val name: String, val age: Int)

// Unstable - holds mutable state
class MutableUser {
    var name: String = ""
}

You can check stability with the @Stable or @Immutable annotations, or inspect the build output with composeCompilerReports.

Composition Lifecycle

  1. Initial composition: Build the entire UI tree from scratch.
  2. Recomposition: Re-execute invalid composables to update the tree.
  3. Disposal: Remove UI nodes that are no longer in the tree.

Understanding this lifecycle is critical for avoiding bugs with side effects.

Performance Implications

Recomposition is fast because Compose only re-executes the smallest possible scope. However, unnecessary recompositions can still hurt performance. Common causes:

  • Passing unstable types as parameters
  • Creating new lambda instances on every recomposition
  • Performing expensive calculations inside composable functions without derivedStateOf or remember

Building Your First Compose App

Anatomy of a Compose Screen

A typical Compose screen is a single composable function that describes the full layout. It takes state as input and returns UI as output.

@Composable
fun TodoScreen(tasks: List<TodoItem>) {
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("My Tasks") })
        }
    ) { padding ->
        LazyColumn(contentPadding = padding) {
            items(tasks) { task ->
                TodoItemRow(task)
            }
        }
    }
}

No fragments. No adapters. No XML. Just functions composed together.

The @Preview Annotation

Compose provides @Preview to render composables in Android Studio without running the app:

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MaterialTheme {
        Greeting("Preview")
    }
}

You can preview multiple configurations:

@Preview(name = "Light Mode", showBackground = true)
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun ThemePreview() {
    MaterialTheme {
        Greeting("Theme Test")
    }
}

Composable Functions Are Regular Functions

Despite the @Composable annotation, these are just Kotlin functions. They can be called from other composables, passed as parameters, and returned from higher-order functions. The annotation signals the Compose compiler to apply special transformations.

@Composable
fun Header() {
    Text("Welcome")
}

@Composable
fun Screen() {
    Column {
        Header()
        Text("Content below")
    }
}

Quiz

1. What is the primary difference between XML layouts and Compose?

Question 1 options

2. What is recomposition in Jetpack Compose?

Question 2 options

3. Which annotation marks a composable for Android Studio preview?

Question 3 options

4. Why might a composable fail to be skipped during recomposition?

Question 4 options

Flashcards

Question

What does declarative UI mean in Compose?

Answer

You describe what the UI should look like for a given state, rather than imperatively mutating views when state changes.

Question

What is the slot table in Compose?

Answer

A data structure generated by the Compose compiler that tracks UI nodes, their positions, and their values -- enabling efficient diffing and targeted updates.

Question

What annotation is required for a function to use Compose APIs?

Answer

@Composable -- it signals the Compose compiler to apply special transformations to the function.

Question

How does Compose handle list rendering compared to RecyclerView?

Answer

LazyColumn and LazyRow provide built-in lazy list behavior without needing adapters, ViewHolders, or XML layouts.

Revision Notes

Key Takeaways

  • 1. Compose replaces XML layouts with Kotlin functions -- no findViewById, no adapters
  • 2. Recomposition is fine-grained: only composable functions with changed inputs re-execute
  • 3. Stable types enable Compose to skip unnecessary recompositions
  • 4. Compose can coexist with XML views via ComposeView and AndroidView

Interview Tips

  • Be ready to explain the difference between composition and recomposition
  • Know why stability matters -- interviewers often ask about recomposition performance
  • Understand the Compose runtime lifecycle: initial composition, recomposition, disposal
  • Compare Compose to other declarative frameworks (SwiftUI, Flutter) for system design discussions

Cheat Sheet

Compose Introduction Cheat Sheet

Paradigm Shift:

  • XML = imperative (find view, mutate view)
  • Compose = declarative (call function with state, UI updates automatically)

Key Concepts:

  • Composition: initial build of the UI tree
  • Recomposition: re-execution of composables with changed inputs
  • Stability: types must be stable for recomposition skipping

Setup:

  • buildFeatures.compose = true in build.gradle.kts
  • Use Compose BOM for version management
  • setContent { } in Activity to set the composable root

Preview:

  • @Preview(showBackground = true) for Android Studio preview
  • Multiple @Preview annotations for different configurations