Jetpack Compose and UI Customization
So far, we have focused entirely on Kotlin logic — how to store data, make decisions, and loop through lists. Today, we finally start building the visual part of our apps. By the end of this lesson, you will know how to create modern, responsive User Interfaces (UI) using Android’s newest design toolkit: Jetpack Compose.
1. XML vs. Jetpack Compose
In Android development, the User Interface (UI) encompasses everything the user interacts with — buttons, text, images, and the layout that organizes them. Historically, Android developers built UIs using a markup language called XML.
- XML (eXtensible Markup Language): With XML, the visual layout is separated entirely from the Kotlin logic. You design the screen in an XML file, and then write complex Kotlin code to connect to those visual elements and update them.
- Jetpack Compose: This is Android’s modern, declarative UI toolkit. Instead of using two different languages, you build your entire UI directly in Kotlin. You simply “declare” (describe) what the UI should look like and how it should behave, and the Compose framework automatically handles rendering and updating it for you.
2. Understanding and Creating Composables
Composables are the building blocks of Jetpack Compose. They are simply Kotlin functions that draw a part of your UI on the screen.
To turn a regular Kotlin function into a Composable, you add the @Composable annotation right above the fun keyword.
@Composable
fun CustomText(message: String) {
Text(text = message)
}Because Composables are just functions, they can take parameters (like the message string above) allowing you to customize them. You can also place one Composable inside another, allowing you to build complex screens out of small, reusable pieces.
@Composable
fun MyApp() {
// We can call our CustomText composable multiple times!
CustomText(message = "Hello, World!")
CustomText(message = "Welcome to Jetpack Compose!")
}3. Arranging Items with Columns and Rows
If you place multiple items inside a Composable, you need to tell Android how to organize them.
Jetpack Compose: Columns
A Column places items vertically, one below the other.
@Composable
fun MyColumn() {
Column {
Text("First item")
Text("Second item")
Text("Third item")
}
}Modifiers, Arrangement, and Alignment
Modifiers are built-in tools that help you customize the appearance and behavior of your UI elements.
.fillMaxSize()is a modifier command that tells an element to stretch and take up all the available space on the screen.
When arranging items in a Column, you can also control their spacing:
- Vertical Arrangement: Decides how items are spaced up-and-down (e.g., pushed to the top, spaced out evenly, or centered).
- Horizontal Alignment: Decides how items are positioned left-and-right (e.g., aligned to the left edge or centered).
@Composable
fun CustomColumn() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}Jetpack Compose: Rows
A Row does the exact same thing as a Column, but it places items horizontally, side-by-side in a line.
@Composable
fun MyRow() {
Row {
Text("First item")
Text("Second item")
Text("Third item")
}
}4. Text Composables
Text composables are parts of the UI where users can either read text or type it themselves.
TextField: A text box where users can type. It comes with a built-in background and an optional label.OutlinedTextField: Similar to aTextField, but it has a clean outline border around it instead of a solid background.BasicTextField: A completely bare-bones text box with no extra design.
@Composable
fun MyOutlinedTextField() {
OutlinedTextField(
value = "",
onValueChange = {},
label = { Text("Enter your email") }
)
}What is onValueChange and State?
In the code above, onValueChange is a listener. Every time you press a key on your keyboard, onValueChange captures the new text so you can save it.
To make a text box actually remember what the user typed, we have to use a specific Jetpack Compose recipe called State. Because we haven’t learned advanced Kotlin properties yet, treat the following line of code as a magic spell that creates a memory box for your UI:
var typedText by remember { mutableStateOf("") }Here is how we use it to make a working text field:
@Composable
fun WorkingTextField() {
// 1. Create the memory box to hold the text
var typedText by remember { mutableStateOf("Type something...") }
Column {
TextField(
value = typedText, // 2. Show whatever is currently in memory
onValueChange = { newText ->
typedText = newText // 3. Update memory when the user types
}
)
Text("You typed: $typedText")
}
}5. The @Preview Composable
The @Preview annotation is like a magical mirror for Android Studio. It allows you to see what your UI looks like right inside the code editor, without needing to run the entire app on a phone or emulator.
Get Aly Route’s stories in your inbox
Join Medium for free to get updates from this writer.
Simply add @Preview above any Composable function that takes no parameters.
@Preview
@Composable
fun PreviewMyColumn() {
MyColumn()
}6. Buttons and onClick
A Button composable is an element users can tap to trigger an action.
When you create a Button, you must give it an onClick command. onClick is the block of Kotlin code that will run the exact moment the button is pressed.
@Composable
fun MyButton() {
Button(
onClick = { println("Button was clicked!") }
) {
Text("Click me")
}
}Note: The code inside the { } after the Button handles what the button looks like (in this case, it holds a Text element).
7. Context and Toasts
What is Context?
In Android, Context is a bridge that connects your app to the Android operating system. It acts like a messenger, helping your app access system resources, launch new screens, or show system-level alerts. You can grab the current context in Compose using LocalContext.current.
What is a Toast?
A Toast is a small, system-level message that pops up briefly at the bottom of the screen (like "Message sent!" or "Saved"). Because it involves the Android system, it requires a Context to work.
To show a Toast, you use Toast.makeText(), provide it the context, the text to display, and the duration (Toast.LENGTH_SHORT or Toast.LENGTH_LONG), and finally call .show().
@Composable
fun ShowToastButton() {
val context = LocalContext.current
Button(
onClick = {
Toast.makeText(context, "Button clicked!", Toast.LENGTH_SHORT).show()
}
) {
Text("Show Toast")
}
}8. Layering with Box and Icons
The Box Composable
While Column arranges items vertically and Row arranges them horizontally, a Box allows you to stack items directly on top of one another. It is perfect for layering—like placing text over an image.
The Icon Composable
An Icon is a small symbol used to represent actions (like a back arrow or a star). Jetpack Compose has many built-in icons you can use instantly.
@Composable
fun StarBox() {
Box(contentAlignment = Alignment.Center) {
// The icon goes on the bottom layer
Icon(
imageVector = Icons.Default.Star,
contentDescription = "Star Icon"
)
// The text goes on the top layer, directly over the star
Text("Star")
}
}Note:
contentDescriptionis used by screen readers to describe the icon to visually impaired users. It is an important part of making your app accessible!.
9. Dropdown Menus
A Dropdown Menu is a hidden list of options that appears when a user clicks on an element. Building one requires a trigger (like a button) and the menu itself, all held together in a Box.
We use a boolean State (true or false) to remember whether the menu should be expanded or hidden.
@Composable
fun DropdownMenuExample() {
// Memory box to track if the menu is open (starts as false)
var expanded by remember { mutableStateOf(false) }
Box {
// The trigger button
IconButton(
onClick = { expanded = !expanded } // Flips false to true
) {
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = "Menu")
}
// The menu itself
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false } // Closes if user taps away
) {
DropdownMenuItem(
text = { Text("Option 1") },
onClick = { /* Do something */ }
)
DropdownMenuItem(
text = { Text("Option 2") },
onClick = { /* Do something */ }
)
}
}
}10. Space vs. Padding
When arranging elements, you need to ensure they aren’t crammed together. In Compose, all spacing is measured in dp (density-independent pixels), which automatically scale based on the user’s screen size.
Padding
Padding is the space inside or around the borders of an element. You apply it using the .padding() modifier. Imagine putting a picture in a frame; the padding is the cardboard border surrounding the photo.
Text("Hello", modifier = Modifier.padding(16.dp))Spacers
A Spacer is a dedicated Composable that acts as an invisible block to push other items apart. Imagine putting an invisible cushion between two items on a shelf.
Column {
Text("Top Item")
Spacer(modifier = Modifier.height(20.dp)) // Adds 20dp of empty vertical space
Text("Bottom Item")
}Conclusion
You have officially taken your first steps into declarative UI! By shifting away from XML, Jetpack Compose allows you to build beautiful, dynamic screens using the exact same Kotlin language you use for your logic.
- You learned how to define UI elements using the
@Composableannotation. - You organized your screens using Parent Containers like
Column(vertical),Row(horizontal), andBox(layered). - You captured user input using
TextFieldand triggered actions usingButtonandonClick. - You connected to the Android system using
Contextto displayToastpopups. - You managed spacing securely using the
Modifier.padding()andSpacertools.
With these foundational visual blocks, you are now ready to start assembling fully interactive, structured screens for your Android apps!







