UI Development (XML & Jetpack Compose)
Compare XML layouts and modern declarative Jetpack Compose UI frameworks, layout design, and component rendering.
Module 7: UI Development (XML & Jetpack Compose)
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
| XML | Compose |
|---|---|
| Imperative updates | Declarative UI |
| XML + Kotlin | Kotlin only |
| View hierarchy | Composition tree |
findViewById / View Binding | Direct function calls |
| Manual updates | State-driven updates |
| RecyclerView | LazyColumn |
| LayoutInflater | Composition 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
ConstraintLayoutover 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
- What is the difference between a
Viewand aViewGroup? - Explain the Measure → Layout → Draw pipeline.
- Why are deep View hierarchies bad for performance?
- What does
LayoutInflaterdo? - Compare
LinearLayout,FrameLayout, andConstraintLayout. - Why is View Binding preferred over
findViewById()? - Explain the difference between imperative and declarative UI.
- What is recomposition in Jetpack Compose?
- What is the purpose of
remember? - How does
LazyColumndiffer conceptually fromRecyclerView?
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.