Android Development 04: Jetpack Compose

What is Jetpack Compose

Jetpack Compose is a modern toolkit for building Android UIs. Compose simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin capabilities. With Compose, you can build your UI by defining a set of functions, called composable functions, that take in data and describe UI elements.

Composable functions

Composable functions are the basic building block of a UI in Compose. A composable function:

  • Describes some part of your UI.

  • Doesn't return anything.

  • Takes some input and generates what's shown on the screen.

  • Annotations can take parameters. Parameters provide extra information to the tools processing them. The following are some examples the @Preview annotation with and without parameters.

  • Jetpack Compose is built around composable functions. These functions let you define your app's UI programmatically by describing how it should look, rather than focusing on the process of the UI's construction. To create a composable function, just add the @Composable annotation to the function name.

  • Composable functions can accept arguments, which let the app logic describe or modify the UI. In this case, your UI element accepts a String so that it can greet the user by name.

@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
    HappyBirthdayTheme {
        Greeting("Android")
    }
}

UI Hierarchy

  • The UI hierarchy is based on containment, meaning one component can contain one or more components, and the terms parent and child are sometimes used.

  • The three basic, standard layout elements in Compose are Column, Row, and Box composables. You learn more about the Box composable in the next codelab.

  • example of child UI in jetpack compose:
@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
    Row {
        Text(
            text = message,
            fontSize = 100.sp,
            lineHeight = 116.sp,
        )
        Text(
            text = from,
            fontSize = 36.sp
        )
    }
}

Example code:

package com.example.happybirthday

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.happybirthday.ui.theme.HappyBirthdayTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HappyBirthdayTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    GreetingText(
                        message = "Happy Birthday Sam!",
                        from = "From Emma",
                        modifier = Modifier.padding(8.dp)
                    )
                }
            }
        }
    }
}

@Composable
fun GreetingText(message: String, from: String, modifier: Modifier = Modifier) {
    Column(
        verticalArrangement = Arrangement.Center,
        modifier = modifier
    ) {
        Text(
            text = message,
            fontSize = 100.sp,
            lineHeight = 116.sp,
            textAlign = TextAlign.Center
        )
        Text(
            text = from,
            fontSize = 36.sp,
            modifier = Modifier
                .padding(16.dp)
                .align(alignment = Alignment.End)
        )
    }
}

@Preview(showBackground = true)
@Composable
fun BirthdayCardPreview() {
    HappyBirthdayTheme {
        GreetingText(message = "Happy Birthday Sam!", from = "From Emma")
    }
}

Explanation:

  • Summary for Interview:

    "This is a Jetpack Compose-based Android app that uses a GreetingText composable to show a birthday message. It uses modern UI practices like theming, column layout, font scaling, and padding. I structured the UI using Surface and MaterialTheme, which ensures design consistency. The use of @Preview allows rapid development and previewing in Android Studio without rebuilding the app every time."

Manual Dependency injection:

  • In the Android Studio "Ladybug" version (which corresponds to Android Studio Hedgehog / Iguana timeframe and uses Gradle 8+, AGP 8+, and Kotlin 1.9+), you still need to manually add the ViewModel dependency if you are using Jetpack Compose and want to integrate ViewModel functionality.

  • Required dependency block:

    // ViewModel for Compose
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
    
    // (Optional) ViewModel core for non-Compose usage
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    

    Replace 2.7.0 with the latest version if a newer one is available.

  • Why Manual?

    Even though Jetpack Compose is now well-integrated in Android Studio templates, not all Compose-related libraries (like viewmodel-compose) are included by default. Android Studio only adds the core Compose libraries in new projects — you’re expected to add additional Jetpack components like ViewModel, Navigation, or Room manually as needed.

Modifier:

In Jetpack Compose, a Modifier is a powerful and flexible UI configuration object that is used to:

Change the layout, appearance, behavior, or interaction of a Composable.

Text(
    text = "Hello, World!",
    modifier = Modifier
        .padding(16.dp)
        .background(Color.Yellow)
        .fillMaxWidth()
)

This means:

  • Add 16dp padding

  • Set yellow background

  • Fill the maximum width

Common Modifier Functions:

Modifier FunctionDescription
padding()Adds padding inside the Composable
fillMaxWidth()Makes the Composable take full width
fillMaxSize()Makes it take full width & height
wrapContentSize()Shrinks to fit content
size()Sets fixed width and height
background()Sets background color or shape
clickable()Makes it respond to clicks
border()Adds a border
align()Aligns inside a parent like Row/Column
offset()Moves the Composable on screen

Why use Modifier?

It separates concerns:

  • Composable = What to show

  • Modifier = How to show it (layout, behavior, style)

@Composable
fun Greeting(name: String) {
    Text(
        text = "Hello, $name!",
        modifier = Modifier
            .padding(16.dp)
            .background(Color.LightGray)
            .clickable { /* handle click */ }
    )
}

Image:

Image insertion steps: link

1. Size & Layout

ModifierDescription
size(100.dp)Sets fixed width and height
width(100.dp) / height(100.dp)Sets width or height separately
fillMaxWidth()Fills entire parent width
fillMaxSize()Fills entire parent area (width & height)
wrapContentSize()Adjusts to image's intrinsic size

2. Styling & Appearance

ModifierDescription
clip(RoundedCornerShape(16.dp))Clips image corners to shape
background(Color.Blue)Sets a background color behind image
border(2.dp, Color.Black)Adds a border
alpha(0.5f)Makes image semi-transparent
graphicsLayer { ... }Applies rotation, scale, etc.

3. Alignment & Positioning

ModifierDescription
align(Alignment.Center)Aligns inside parent layout
offset(x = 10.dp, y = 5.dp)Moves image from its original position
padding(16.dp)Adds space around the image
zIndex(1f)Sets the layering order of UI elements

4. Interactivity

ModifierDescription
clickable { ... }Makes image respond to taps/clicks
pointerInput { ... }Detect drag, zoom, gestures

Example:

Image(
    painter = painterResource(id = R.drawable.sample_image),
    contentDescription = "Sample Image",
    modifier = Modifier
        .size(200.dp)
        .padding(16.dp)
        .clip(RoundedCornerShape(12.dp))
        .border(2.dp, Color.Gray)
        .clickable { /* onClick action */ }
)
0
Subscribe to my newsletter

Read articles from Dibyashree Chakravarty directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dibyashree Chakravarty
Dibyashree Chakravarty