Android
01 / 03

Jetpack Compose UI

Android: Jetpack Compose UI

Jetpack Compose is Android's modern declarative UI toolkit. Instead of XML layouts, you write Kotlin functions annotated with @Composable that describe the UI. Compose re-runs composables when state changes — no manual view updates.

Composable Functions

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

// Stateful composable — owns and manages state
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Count: $count", style = MaterialTheme.typography.headlineMedium)
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

// Stateless composable — receives state as params (easier to test/reuse)
@Composable
fun CounterDisplay(count: Int, onIncrement: () -> Unit) {
    Column {
        Text("Count: $count")
        Button(onClick = onIncrement) { Text("Increment") }
    }
}

// Preview
@Preview(showBackground = true)
@Composable
fun CounterPreview() {
    MyAppTheme { Counter() }
}

Layout Composables

// Column — vertical stack
Column(
    modifier = Modifier.fillMaxWidth().padding(16.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Text("Item 1")
    Text("Item 2")
}

// Row — horizontal stack
Row(
    modifier = Modifier.fillMaxWidth(),
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically
) {
    Text("Left")
    Icon(Icons.Default.ArrowForward, contentDescription = null)
}

// Box — overlapping (Z-stack)
Box(modifier = Modifier.fillMaxSize()) {
    Image(painter = painterResource(R.drawable.bg), contentDescription = null,
        modifier = Modifier.fillMaxSize())
    Text("Overlay text", modifier = Modifier.align(Alignment.Center))
}

// LazyColumn — scrollable list (RecyclerView equivalent)
LazyColumn(contentPadding = PaddingValues(16.dp)) {
    items(items, key = { it.id }) { item ->
        ItemCard(item = item, onClick = { onItemClick(item) })
    }
    item { Spacer(modifier = Modifier.height(80.dp)) }  // bottom padding
}

Modifier

// Modifier chains — order matters
Modifier
    .fillMaxSize()                         // fill parent
    .fillMaxWidth().height(56.dp)          // fixed height, full width
    .size(48.dp)                           // square
    .padding(horizontal = 16.dp, vertical = 8.dp)
    .background(MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp))
    .clip(RoundedCornerShape(8.dp))        // clip to shape
    .border(1.dp, Color.Gray, RoundedCornerShape(8.dp))
    .clickable { onClick() }
    .clickable(onClick = onClick, role = Role.Button)
    .semantics { contentDescription = "Increment counter" }  // accessibility
    .testTag("counter_button")             // UI testing
    .weight(1f)                            // in Row/Column: take remaining space

Common Components

// Text field
var text by remember { mutableStateOf("") }
OutlinedTextField(
    value = text,
    onValueChange = { text = it },
    label = { Text("Email") },
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
    isError = !text.contains("@"),
    supportingText = { if (!text.contains("@")) Text("Invalid email") }
)

// Image loading (Coil)
// implementation("io.coil-kt:coil-compose:2.6.0")
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = "Profile photo",
    modifier = Modifier.size(40.dp).clip(CircleShape),
    contentScale = ContentScale.Crop,
    placeholder = painterResource(R.drawable.placeholder),
    error = painterResource(R.drawable.error),
)

// Scaffold — standard screen layout
Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("My App") },
            navigationIcon = {
                IconButton(onClick = { navController.popBackStack() }) {
                    Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                }
            }
        )
    },
    floatingActionButton = {
        FloatingActionButton(onClick = { /* add item */ }) {
            Icon(Icons.Default.Add, contentDescription = "Add")
        }
    }
) { paddingValues ->
    Content(modifier = Modifier.padding(paddingValues))
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free