Skip to content
Module 7 of 20

Jetpack Compose vs XML

Jetpack Compose vs XML: View hierarchy, declarative UI, state, recomposition, and when each layout system still wins.

Learning Objectives

By the end of this module, you’ll understand:

  • How Android renders a UI
  • The View hierarchy
  • The rendering pipeline (Measure → Layout → Draw)
  • XML layouts and their role
  • LayoutManagers (LinearLayout, FrameLayout, ConstraintLayout)
  • View Binding vs Data Binding
  • Event handling
  • Jetpack Compose architecture
  • Declarative vs Imperative UI
  • State and recomposition
  • Performance considerations
  • Best practices

Part 1 – The Android View System


1. What Is a View?

Everything you see on an Android screen is a View.

Examples:

Button

TextView

ImageView

EditText

RecyclerView

Switch

ProgressBar

All of them inherit from the same base class:

View

Think of View as the fundamental building block of the Android UI toolkit.


The View Hierarchy

Imagine this screen:

+-------------------------+
| Welcome                 |
|                         |
| Username [________]     |
| Password [________]     |
|                         |
|   [ Login ]             |
+-------------------------+

Internally, Android represents it as a tree.

ConstraintLayout
│
├── TextView
├── EditText
├── EditText
└── Button

Every Android screen is a tree.

Not a list.

Not a graph.

A tree.


2. Why a Tree?

Suppose you have:

ConstraintLayout

↓

LinearLayout

↓

Button

If the parent moves:

The child moves.

If the parent becomes invisible:

The child disappears.

If the parent rotates:

The child rotates.

This parent-child relationship makes UI management predictable.


3. ViewGroups

Some Views contain other Views.

These are called ViewGroups.

View

↓

ViewGroup

↓

ConstraintLayout

↓

Children

Examples:

  • ConstraintLayout
  • LinearLayout
  • FrameLayout
  • RelativeLayout (legacy)

A Button cannot contain another View.

A ConstraintLayout can.


4. XML Layouts

Instead of creating every View in Kotlin:

val button = Button(this)
button.text = "Login"

Android lets us describe UI declaratively in XML.

<Button
    android:text="Login"/>

The XML file is not the UI.

It is a blueprint.

During runtime:

activity_main.xml

↓

LayoutInflater

↓

Real Views

↓

Displayed

5. LayoutInflater

One of Android’s most important classes.

When Activity executes:

setContentView(R.layout.activity_main)

Internally:

XML

↓

LayoutInflater

↓

Creates View Objects

↓

Returns Root View

The XML is parsed.

Real Kotlin/Java objects are created.


6. The Three Rendering Phases

This is one of the most important concepts in Android UI.

Every View goes through three phases.

Measure

↓

Layout

↓

Draw

Every frame.

Every screen.

Every View.


Phase 1 — Measure

Question:

“How big do you want to be?”

The parent asks every child.

Example:

Button

↓

Width?

↓

Height?

The Button answers.

Maybe:

120dp

48dp

Every View measures itself.


Phase 2 — Layout

Now the parent asks:

“Where should you be positioned?”

Example:

Button

↓

x = 30

y = 200

The View now knows its location.


Phase 3 — Draw

Finally:

Android renders pixels.

Canvas

↓

Button

↓

Text

↓

Background

↓

Border

Only now does the user actually see something.


7. The Complete Rendering Pipeline

XML

↓

Inflate Views

↓

Measure

↓

Layout

↓

Draw

↓

Display

This happens far more often than many developers realize.

Changing a View’s size triggers another measure/layout pass.

Changing only its text color usually triggers just a redraw.

Understanding this helps you write efficient UI code.


8. Common Layouts


LinearLayout

Children arranged in one direction.

Vertical:

Title

↓

Username

↓

Password

↓

Login

Horizontal:

Icon  Text  Arrow

Simple.

Easy.

Limited.


FrameLayout

Think of stacked paper.

Image

↓

Progress Bar

↓

Text

Children overlap.

Great for:

  • Fragment containers
  • Image overlays
  • Loading indicators

ConstraintLayout

The most powerful XML layout.

Instead of nesting:

Linear

↓

Linear

↓

Linear

↓

Linear

Everything becomes constrained.

Button

↓

Constrained

↓

Parent Bottom

Advantages:

  • Flat hierarchy
  • Better performance
  • Flexible positioning

Most XML-based production apps use ConstraintLayout as the root layout.


9. Why Deep View Hierarchies Hurt Performance

Imagine:

LinearLayout

↓

LinearLayout

↓

LinearLayout

↓

LinearLayout

↓

Button

Each level:

  • Measures
  • Lays out
  • Draws

Deep trees increase work.

Flatter hierarchies are generally faster.

This is one reason ConstraintLayout became popular.


10. Finding Views

Old approach:

findViewById(R.id.button)

Problems:

  • Verbose
  • Unsafe casts (before generics improvements)
  • Boilerplate

Modern approach:

View Binding.

binding.loginButton.text = "Login"

Compile-time safety.

No manual lookup.


11. View Binding

Generated automatically.

Suppose:

activity_main.xml

Android generates:

ActivityMainBinding

Instead of:

findViewById(...)

You write:

binding.loginButton

Benefits:

  • Type safety
  • No null checks for existing views
  • Less boilerplate

12. Event Handling

A View receives user input.

Example:

button.setOnClickListener {

}

Flow:

User Touch

↓

Android

↓

Button

↓

Click Listener

↓

Your Code

Notice again:

Android delivers the event.

You react.


Part 2 – Jetpack Compose

Now we shift to modern Android.


13. Why Compose Exists

Imagine updating one TextView.

Old View system:

Find View

↓

Modify View

↓

Invalidate

↓

Redraw

As UIs became more dynamic, manually keeping Views in sync with data became increasingly complex.

Google introduced Compose.


14. Imperative vs Declarative UI

XML / View System

Imperative.

You tell Android:

“Do this.”

Example:

textView.text = "Hello"
button.visibility = View.GONE

You manually mutate existing UI objects.


Compose

Declarative.

You describe:

“Given this state, the UI should look like this.”

Example:

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

You describe the desired UI, not the steps to update it.


15. Composable Functions

Every UI element is a function.

@Composable
fun LoginScreen() {

}

Unlike Activities or Fragments:

Composable functions don’t own windows.

They emit UI into a composition managed by Compose.


16. Compose UI Tree

Just like XML has a View tree,

Compose builds a composition tree.

Column

↓

Text

↓

Button

↓

Image

Conceptually similar.

Implementation completely different.


17. State

State is the heart of Compose.

Imagine:

var count = 0

Changing it won’t update the UI.

Compose needs observable state.

var count by remember {
    mutableStateOf(0)
}

Now:

State Changes

↓

Compose Notices

↓

Recomposition

↓

UI Updates

18. Recomposition

This is Compose’s superpower.

Example:

Text("$count")

When:

count++

↓

State Changed

Compose recomposes only the parts of the UI that depend on count.

Not the entire screen.


19. Remember

Without:

val name = ""

Every recomposition recreates the variable.

With:

remember {
    mutableStateOf("")
}

The value survives recompositions (but not Activity recreation).

Think of remember as “retain this value while this composable remains in the composition.”


20. Modifier

Instead of LayoutParams:

Compose uses Modifiers.

Modifier
    .padding(16.dp)
    .fillMaxWidth()

Modifiers are immutable objects that decorate how a composable is laid out, drawn, or interacts with input.


21. Compose Layouts

Instead of XML:

<LinearLayout>

Compose:

Column {

}

Horizontal:

Row {

}

Stack:

Box {

}

Constraint-based layouts are also available in Compose when needed, but Column, Row, and Box cover many common layouts.


22. LazyColumn

Equivalent of RecyclerView.

Old:

RecyclerView

↓

Adapter

↓

ViewHolder

Compose:

LazyColumn {

}

Compose creates and disposes visible items lazily, similar to how RecyclerView recycles views, though the internal implementation differs.


23. XML vs Compose

XMLCompose
Imperative updatesDeclarative UI
XML + KotlinKotlin only
View hierarchyComposition tree
findViewById / View BindingDirect function calls
Manual updatesState-driven updates
RecyclerViewLazyColumn
LayoutInflaterComposition engine

24. Common Compose Mistakes

Mistake 1

Keeping UI state in a normal variable.

Use observable state (mutableStateOf, StateFlow, etc.).


Mistake 2

Doing expensive work inside a composable.

Composable functions may execute many times due to recomposition.

Move long-running work to a ViewModel or side-effect APIs like LaunchedEffect.


Mistake 3

Thinking recomposition recreates everything.

Compose skips unchanged parts whenever possible.

Recomposition is generally much more granular than redrawing an entire screen.


25. Modern Architecture

A typical Compose app:

Activity

↓

NavHost

↓

Composable

↓

ViewModel

↓

Repository

↓

Network

Notice how the Activity becomes much thinner.

Its main responsibility is hosting the Compose UI.


Best Practices

  • Keep your View hierarchy shallow.
  • Prefer ConstraintLayout over deeply nested XML layouts when using the View system.
  • Use View Binding instead of findViewById().
  • Keep business logic out of Activities, Fragments, and composables.
  • Make UI a function of state.
  • Hoist state to the appropriate owner (often a ViewModel).
  • Treat composables as lightweight, side-effect-free descriptions of UI whenever possible.

Mental Model

The biggest conceptual shift in this module is this:

View System

Data Changes

↓

Developer Updates UI

↓

UI Changes

You tell Android how to update the interface.


Compose

Data Changes

↓

State Changes

↓

Compose Recomposition

↓

UI Automatically Matches State

You describe what the UI should look like for a given state.

This single shift—from imperative UI manipulation to declarative state-driven UI—is why Compose feels so different and why many developers find it more productive once they embrace its model.


Interview Questions

  1. What is the difference between a View and a ViewGroup?
  2. Explain the Measure → Layout → Draw pipeline.
  3. Why are deep View hierarchies bad for performance?
  4. What does LayoutInflater do?
  5. Compare LinearLayout, FrameLayout, and ConstraintLayout.
  6. Why is View Binding preferred over findViewById()?
  7. Explain the difference between imperative and declarative UI.
  8. What is recomposition in Jetpack Compose?
  9. What is the purpose of remember?
  10. How does LazyColumn differ conceptually from RecyclerView?

Next Module: Navigation & Intents (One of Android’s Core Concepts)

We’ll answer questions like:

  • What exactly is an Intent?
  • How does Android know which Activity to launch?
  • What’s the difference between explicit and implicit intents?
  • How do apps communicate with each other?
  • What are tasks, back stacks, and launch modes?
  • How does the Navigation Component work?
  • How does navigation differ between XML-based apps and Compose apps?

This module ties together Activities, Fragments, and the Android system itself, and it’s essential for understanding how Android applications move users through different parts of an app.