
Welcome back to the Compose series on codewithpk.com π
In the last chapter, we created our first Compose project and explored the structure.
Now itβs time to build real UI β text, images, buttons, and input fields β and understand the magic ingredient behind Compose: State.
This is the chapter where things actually start moving on screen.
Letβs go step by step.
π 1. The Text Composable
The simplest composable in Jetpack Compose is Text().
Itβs the Compose version of TextView, but way more powerful and customizable.
Hereβs the basic syntax:
@Composable
fun TextExample() {
Text(text = "Hello, Jetpack Compose!")
}
Now, letβs make it look less boring.
You can customize text with properties like fontSize, fontStyle, fontWeight, color, and textAlign.
Example:
@Preview(showBackground = true)
@Composable
fun StyledText() {
Text(
text = "Hello, CodeWithPK!",
fontSize = 36.sp,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.ExtraBold,
color = Color.Red,
textAlign = TextAlign.Center
)
}
π‘ Dev Trick:
- Use
sp(scale-independent pixels) for font size. - Use
dpfor dimensions (width, height, padding). - Always import colors from
androidx.compose.ui.graphics.Color, notandroid.graphics.Color.
If you hold Ctrl + Click on the Text() composable in Android Studio, youβll see all available properties β like maxLines, overflow, and fontFamily.
πΌοΈ 2. The Image Composable
Next up β images.
In the XML world, we used ImageView.
In Compose, youβll use the Image() composable.
Hereβs a simple example using a drawable resource:
@Preview(showBackground = true)
@Composable
fun ImageExample() {
Image(
painter = painterResource(id = R.drawable.heart_icon),
contentDescription = "Heart Icon β€οΈ"
)
}
π‘ Key Properties You Should Know:
| Property | What it does |
|---|---|
contentDescription |
Accessibility label for screen readers |
colorFilter |
Tint or recolor the image |
contentScale |
Defines how the image scales inside the layout (Crop, Fit, FillBounds) |
Example with tint:
Image(
painter = painterResource(R.drawable.heart_icon),
contentDescription = "Blue Heart",
colorFilter = ColorFilter.tint(Color.Blue),
contentScale = ContentScale.Crop
)
π‘ Dev Trick:
Want to load an image from a URL?
Use Coil β a third-party image loader built for Compose.
implementation("io.coil-kt:coil-compose:2.6.0")
Example:
Image(
painter = rememberAsyncImagePainter("https://example.com/photo.jpg"),
contentDescription = "Remote Image"
)
π 3. The Button Composable
Buttons in Compose are cleaner than the XML nightmares we used to deal with.
Example:
@Preview(showBackground = true)
@Composable
fun ButtonExample() {
Button(
onClick = { println("Button Clicked!") },
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black,
contentColor = Color.White
),
enabled = true
) {
Text("Click Me")
}
}
π‘ Dev Trick:
You can place anything inside a Button β text, icons, even images.
Example:
Button(onClick = { /* TODO */ }) {
Icon(Icons.Default.Favorite, contentDescription = "Like")
Spacer(modifier = Modifier.width(8.dp))
Text("Like")
}
You can also customize elevation, border, or shape:
Button(
onClick = { /* Click */ },
border = BorderStroke(2.dp, Color.Red),
shape = RoundedCornerShape(12.dp)
) {
Text("Rounded Button")
}
βοΈ 4. TextField β Taking User Input
Now for one of the most important composables: TextField().
Think of it as the Compose version of EditText, but 100x easier to manage.
Basic usage:
@Preview(showBackground = true)
@Composable
fun TextFieldExample() {
TextField(
value = "Hello CodeWithPK",
onValueChange = {},
label = { Text("Your Name") },
placeholder = { Text("Enter text here...") }
)
}
Looks good, right?
But if you type something in the previewβs interactive mode, nothing happens.
Why?
Because we hardcoded the value.
We need state.
π 5. Understanding State in Jetpack Compose
Jetpack Compose is data-driven.
That means your UI reflects whatever state (data) you give it.
If the data changes, Compose automatically recomposes the affected UI β no notifyDataSetChanged(), no invalidateViews(), nothing.
Letβs fix our TextField to actually work.
@Preview(showBackground = true)
@Composable
fun TextFieldWithState() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter Something") },
placeholder = { Text("Type here...") }
)
}
Hereβs whatβs happening:
mutableStateOf("")creates a reactive variable.rememberensures the state isnβt reset during recomposition.- When the user types,
onValueChangeupdates the state. - Compose detects the change and redraws the UI automatically.
π‘ Dev Trick:
If you forget remember, the state resets every time Compose recomposes.
Thatβs the #1 reason your TextField wonβt hold user input.
π§ Behind the Scenes: Composition & Recomposition
When you run this example, Compose follows three steps:
- Initial Composition β It renders your UI for the first time using the current data.
- Event Handling β The user interacts with the app (e.g., types text or clicks a button).
- Recomposition β Compose re-calls your composable with the new data and updates only the changed parts.
Example Flow:
User types "A" β state changes β recomposition β UI updates instantly
No manual UI updates, no lifecycle juggling.
π‘ Fun Fact: This reactive pattern is similar to how React, SwiftUI, and Flutter work β but Compose does it natively with Kotlin.
π 6. Bonus: Combine Composables
Because everything in Compose is modular, you can combine multiple composables together easily.
Example: a simple login card.
@Preview(showBackground = true)
@Composable
fun LoginCard() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Login", fontSize = 28.sp, fontWeight = FontWeight.Bold)
TextField(value = username, onValueChange = { username = it }, label = { Text("Username") })
TextField(value = password, onValueChange = { password = it }, label = { Text("Password") })
Button(onClick = { println("Username: $username, Password: $password") }) {
Text("Submit")
}
}
}
Congrats β you just built a fully functional form with no XML and no boilerplate.
π§© Final Thoughts
In this chapter, youβve learned:
β
How to use basic composables β Text, Image, Button, and TextField
β
How to style and customize them
β
How state drives UI and recomposition
β
And how to combine everything into interactive, dynamic layouts
The takeaway?
βIn Jetpack Compose, you donβt update the UI β you update the data, and the UI updates itself.β – codewithpk.com
Next up in Chapter 4, weβll dive into Layouts and Modifiers β how to arrange composables beautifully using Row, Column, Box, and all those secret layout powers that make Compose addictive.
Made with β€οΈ by codewithpk.com
Keep coding. Keep learning. Keep building.
