PCSalt
YouTube GitHub
Back to Jetpack Compose
Jetpack Compose · 2 min read

Jetpack Compose — Your First Screen (Text, Button, Column, Row)

Build your first Jetpack Compose screen from scratch — Text, Button, Column, Row, modifiers, and previews. Everything you need to start building UI in Compose.


This is Part 1 of a 10-part series on Jetpack Compose.

  1. Your First Screen (this post)
  2. State Management
  3. Navigation
  4. Lists
  5. Theming
  6. Forms
  7. Side Effects
  8. Custom Layouts
  9. API Integration
  10. Migration from XML

What is Jetpack Compose?

Jetpack Compose is Android’s modern toolkit for building UI. Instead of XML layouts and findViewById, you write UI as Kotlin functions. The framework handles rendering and updates.

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

That’s a composable function. It takes data, returns UI. No XML, no view binding, no inflation. Compose has been stable since July 2021 and is now the recommended way to build Android UI.

Setup

Add Compose dependencies to your build.gradle.kts:

android {
    buildFeatures {
        compose = true
    }
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:2026.02.01")
    implementation(composeBom)
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.10.1")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

The BOM (Bill of Materials) manages version alignment — you specify the BOM version and all Compose libraries use compatible versions.

Your first composable

In your Activity:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    Greeting("Compose")
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

setContent replaces setContentView. Inside it, you call composable functions. MaterialTheme applies Material Design 3 styling. Surface provides a background.

Text

import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

@Composable
fun TextExamples() {
    // Basic text
    Text(text = "Hello, World!")

    // Styled text
    Text(
        text = "Bold and large",
        fontSize = 24.sp,
        fontWeight = FontWeight.Bold
    )

    // Material typography
    Text(
        text = "Headline",
        style = MaterialTheme.typography.headlineMedium
    )

    // Text with color
    Text(
        text = "Colored text",
        color = MaterialTheme.colorScheme.primary
    )
}

Use MaterialTheme.typography for consistent text styles across the app. Avoid hardcoding sizes — let the theme handle it.

Button

import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.TextButton
import androidx.compose.material3.Text

@Composable
fun ButtonExamples() {
    // Filled button (primary action)
    Button(onClick = { /* handle click */ }) {
        Text("Save")
    }

    // Outlined button (secondary action)
    OutlinedButton(onClick = { /* handle click */ }) {
        Text("Cancel")
    }

    // Text button (tertiary action)
    TextButton(onClick = { /* handle click */ }) {
        Text("Skip")
    }
}

Buttons take an onClick lambda and a content lambda. The content is typically Text, but can be any composable (icons, rows, etc.).

Column — Vertical layout

Column stacks children vertically. It’s the Compose equivalent of a vertical LinearLayout.

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun ProfileCard() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Alice Johnson",
            style = MaterialTheme.typography.headlineSmall
        )
        Text(
            text = "[email protected]",
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
        Text(
            text = "Software Developer",
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}

Alignment and arrangement

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Alignment

@Composable
fun CenteredContent() {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Centered vertically and horizontally")
    }
}
  • verticalArrangement controls spacing between children: Top, Center, Bottom, SpaceBetween, SpaceEvenly, SpaceAround
  • horizontalAlignment aligns children horizontally: Start, CenterHorizontally, End

Row — Horizontal layout

Row places children side by side. Equivalent of a horizontal LinearLayout.

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star

@Composable
fun RatingRow(rating: Float) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Icon(
            imageVector = Icons.Default.Star,
            contentDescription = "Rating",
            tint = MaterialTheme.colorScheme.primary
        )
        Spacer(modifier = Modifier.width(4.dp))
        Text(
            text = "$rating / 5.0",
            style = MaterialTheme.typography.bodyMedium
        )
    }
}

Spacer adds empty space. Modifier.width() sets its size.

Modifiers — The styling system

Modifiers are how you style, position, and add behavior to composables. They chain together:

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip

@Composable
fun StyledBox() {
    Text(
        text = "Click me",
        modifier = Modifier
            .fillMaxWidth()
            .clip(RoundedCornerShape(8.dp))
            .background(MaterialTheme.colorScheme.primaryContainer)
            .clickable { /* handle click */ }
            .padding(16.dp)
    )
}

Order matters. Modifiers apply from outer to inner:

// Padding outside the background (margin-like)
Modifier
    .padding(16.dp)
    .background(Color.Red)

// Padding inside the background
Modifier
    .background(Color.Red)
    .padding(16.dp)

Common modifiers

ModifierPurpose
padding(dp)Inner spacing
fillMaxWidth()Take full width
fillMaxSize()Take full width and height
size(dp)Fixed size
weight(float)Proportional size in Row/Column
background(color)Background color
clip(shape)Clip to shape
clickable { }Handle clicks
border(width, color)Border

Box — Layered layout

Box stacks children on top of each other. Like a FrameLayout:

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment

@Composable
fun LoadingOverlay(isLoading: Boolean) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Content here")
        if (isLoading) {
            CircularProgressIndicator()
        }
    }
}

Use Box for overlapping content — loading indicators over content, badges on icons, floating action buttons.

Building a real screen

Let’s combine everything into a simple profile screen:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun ProfileScreen(
    name: String,
    email: String,
    bio: String,
    postCount: Int,
    followerCount: Int,
    onEditClick: () -> Unit,
    onLogoutClick: () -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // Avatar placeholder
        Box(
            modifier = Modifier
                .size(80.dp)
                .clip(CircleShape)
                .background(MaterialTheme.colorScheme.primaryContainer),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = name.first().toString(),
                style = MaterialTheme.typography.headlineLarge,
                color = MaterialTheme.colorScheme.onPrimaryContainer
            )
        }

        Spacer(modifier = Modifier.height(16.dp))

        Text(
            text = name,
            style = MaterialTheme.typography.headlineSmall
        )

        Text(
            text = email,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )

        Spacer(modifier = Modifier.height(8.dp))

        Text(
            text = bio,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )

        Spacer(modifier = Modifier.height(24.dp))

        // Stats row
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            StatItem(label = "Posts", count = postCount)
            StatItem(label = "Followers", count = followerCount)
        }

        Spacer(modifier = Modifier.height(24.dp))
        HorizontalDivider()
        Spacer(modifier = Modifier.height(24.dp))

        // Action buttons
        Button(
            onClick = onEditClick,
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Edit Profile")
        }

        Spacer(modifier = Modifier.height(8.dp))

        OutlinedButton(
            onClick = onLogoutClick,
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Log Out")
        }
    }
}

@Composable
fun StatItem(label: String, count: Int) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(
            text = count.toString(),
            style = MaterialTheme.typography.titleLarge
        )
        Text(
            text = label,
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}

Every composable takes its data as parameters. No findViewById, no view binding, no XML inflation. The UI is a function of its inputs.

Previews

Preview composables in Android Studio without running the app:

import androidx.compose.ui.tooling.preview.Preview

@Preview(showBackground = true)
@Composable
fun ProfileScreenPreview() {
    MaterialTheme {
        ProfileScreen(
            name = "Alice Johnson",
            email = "[email protected]",
            bio = "Android developer and coffee enthusiast",
            postCount = 42,
            followerCount = 1200,
            onEditClick = {},
            onLogoutClick = {}
        )
    }
}

@Preview renders the composable in the IDE’s preview pane. Add showBackground = true for a white background. You can create multiple previews — light mode, dark mode, different screen sizes.

Key concepts to remember

  1. Composables are functions — they take parameters and describe UI
  2. Modifiers are how you style — chain them, order matters
  3. Column = vertical, Row = horizontal, Box = layered
  4. Use Material themeMaterialTheme.typography, MaterialTheme.colorScheme
  5. Preview everything@Preview lets you iterate fast

Next up: State Management in Compose — how to make your UI interactive with remember, mutableStateOf, and ViewModel.