Introduction to Compose
Think of Jetpack Compose as a modern way to build Android apps, like using LEGO blocks instead of traditional building materials. Just as LEGO blocks snap together to create structures, Compose lets you build user interfaces by combining small, reusable pieces called composables.
In this lesson, we'll explore how Jetpack Compose revolutionizes Android development by allowing you to write your entire user interface in Kotlin code. We'll learn about composables, how they work together, and why Compose is becoming the preferred way to build Android apps.
Quick Reference Table
| Concept | Description | When to Use |
|---|---|---|
| Composable | Building block for UI elements | When creating any part of your UI |
| Text | Displays text on screen | When showing text to users |
| Button | Interactive element users can tap | When users need to perform actions |
| Column | Arranges items vertically | When stacking elements |
| Row | Arranges items horizontally | When placing elements side by side |
Compose Basics
When to Use
- When building new Android apps
- When you want to write UI code in Kotlin
- When you need a more modern approach to Android development
- When you want to create responsive UIs that update automatically
| Feature | What It Does | When to Use It |
|---|---|---|
| Declarative UI | Describes what the UI should look like | When you want to focus on the end result |
| Composable Functions | Creates reusable UI components | When you need to reuse UI elements |
| State Management | Handles data changes automatically | When your UI needs to update based on data |
| Layout System | Arranges UI elements on screen | When you need to position elements |
A Few Examples
// Basic text display
fun Greeting() {
Text("Hello, world!")//notice the text does not have brackets {..}
}
// Interactive button
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
// Layout with multiple elements
fun ProfileCard(name: String, role: String) {
Column (modifier = Modifier.padding(16.dp)) // Notice the column here has (...) this is calling parameters
//The curly braces here is a trailing lambda passed as the last paremeter of the function. In Compose, that trailing lambda is the content block (sometimes called a content lambda or slot).
{
Text(name, style = MaterialTheme.typography.h5)
Text(role, style = MaterialTheme.typography.body1)
Button(onClick = { /* Handle click */ }) { //notice the button does have brackets {...}
Text("Contact")
}
}
}
What These Examples Are Doing
Here’s what each snippet does in plain terms:
- Greeting: A minimal composable that only shows the text "Hello, world!" on the screen. There are no parameters and no child elements—just one
Textcall. - Counter: A small interactive example. It uses
remember { mutableStateOf(0) }to keep a number in memory. The screen shows "Count: 0" (or whatever the current count is) and a button labeled "Increment." Each time you tap the button,count++runs, the count updates, and Compose redraws the UI so the new number appears. This is a simple example of state driving the UI. - ProfileCard: A composable that takes a
nameandroleas parameters. It puts them in aColumn(so they stack vertically), with the name in a larger heading style and the role in body text, plus a "Contact" button at the bottom. So you can reuse the same card for different people by passing different names and roles.
Why Some Composables Use Brackets and Others Don't
The difference comes down to whether the composable can contain children:
Composables Without a Content Lambda → Just (...)
Text("Hello World")
Textdoesn't expect child composables.- It just draws some text, and its parameters (text, modifier, style, etc.) are enough.
- Therefore, you only pass arguments in parentheses.
Composables With a Content Lambda → (...){ ... }
Button(onClick = { /* do something */ }) {
Text("Click Me")
}
Buttonis a container composable.- It needs:
- Parameters (like
onClick) → go in the(...). - Child content (UI inside the button, like
Text) → goes in the trailing{ ... }lambda.
- Parameters (like
- That trailing lambda is called the content slot or content lambda.
Why This Matters
Think of it like HTML:
Text(...)is like<span>Hello</span>→ no nested tags.Button(...){ ... }is like<button><span>Click Me</span></button>→ it wraps other UI elements.
Compose vs. Traditional Android
When to Use
- Use Compose for new projects or modernizing existing apps
- Use traditional XML layouts when maintaining older apps
- Use Compose when you want to write UI code in Kotlin
| Approach | What It Does | When to Use It |
|---|---|---|
| Traditional (XML) | Separates layout and logic | For older Android apps |
| Compose | Combines layout and logic | For modern Android apps |
Practical Examples
// Traditional XML approach
<TextView
android:id="@+id/textView"
android:text="Hello, world!"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
// Traditional Kotlin approach
val textView = findViewById<TextView>(R.id.textView)
textView.text = "Hello, world!"
// Compose approach
fun Greeting() {
Text("Hello, world!")
}
What These Three Approaches Are Doing
All three snippets show "Hello, world!" on the screen, but in different ways:
- Traditional XML: The first block is an XML layout file. It defines a
TextViewwith an id and the text "Hello, world!". The layout and the text are declared in XML, separate from your Kotlin code. - Traditional Kotlin: The second block is Kotlin code that runs after the screen is built. It finds the
TextViewby its id (findViewById) and then sets its text. So you have two steps: define the UI in XML, then change it from Kotlin. - Compose approach: The third block is a Compose function. It describes the UI directly in Kotlin: when
Greeting()is called, it draws aTextthat says "Hello, world!". There is no separate XML file and nofindViewById—just one function that builds the UI.
Tips for Success
- Start with simple composables and build up to more complex ones
- Use the Compose Preview feature to see your UI as you build it
- Break down complex UIs into smaller, reusable composables
- Use Material Design components for a consistent look and feel
- Learn about state management early to create interactive UIs
- Use the Compose documentation and samples as reference
Common Mistakes to Avoid
- Forgetting to use the @Composable annotation on composable functions
- Not handling state properly, leading to UI not updating
- Creating overly complex composables that are hard to maintain
- Not using the Compose Preview feature to check your UI
- Mixing traditional XML layouts with Compose unnecessarily
- Not following Material Design guidelines for consistency
Best Practices
- Keep composables small and focused on a single responsibility
- Use meaningful names for your composable functions
- Extract reusable UI elements into separate composables
- Use the Compose Preview feature to iterate quickly
- Follow Material Design guidelines for a consistent look
- Test your composables with different screen sizes and orientations