How to create a Hello World app with Jetpack Compose

Build a minimal Jetpack Compose app from scratch — understand every file, every composable, and every piece of the puzzle.

, updated

A Hello World in Jetpack Compose is more than just printing text — it’s your first encounter with composables, themes, and the Compose UI tree. This guide walks through building a complete minimal app and explains every part.

The fastest way: New Project

In Android Studio: File → New → New Project → Empty Compose Activity. This generates a working Compose project. But let’s understand what it creates and how to build one from scratch.

Project structure

A minimal Compose project looks like this:

HelloCompose/
├── app/
│   ├── build.gradle.kts          # App-level Gradle config + Compose deps
│   └── src/main/
│       ├── AndroidManifest.xml    # App declaration
│       ├── java/com/example/hello/
│       │   ├── MainActivity.kt     # Entry point — sets content
│       │   └── ui/theme/          # Theme, colors, typography
│       │       ├── Theme.kt
│       │       ├── Color.kt
│       │       └── Type.kt
│       └── res/
│           └── values/
│               └── strings.xml
├── build.gradle.kts               # Project-level Gradle config
├── settings.gradle.kts
└── gradle.properties

Step 1: Project-level build.gradle.kts

Configure Kotlin and the Compose compiler plugin:

plugins {
    id("com.android.application") version "8.5.0" apply false
    id("org.jetbrains.kotlin.android") version "2.0.0" apply false
    id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" apply false
}

Step 2: App-level build.gradle.kts

Add the Compose dependencies using the BOM:

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")
}

android {
    namespace = "com.example.hello"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.example.hello"
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
    }

    buildFeatures {
        compose = true
    }

    kotlin {
        jvmToolchain(17)
    }
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:2024.06.00")
    implementation(composeBom)

    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.foundation:foundation")
    implementation("androidx.activity:activity-compose:1.9.0")

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

Step 3: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.Material3.DynamicColors.DayNight">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

Step 4: The MainActivity

This is the entry point. setContent bridges the Android Activity world into Compose:

package com.example.hello

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "World",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

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

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MaterialTheme {
        Greeting(name = "World")
    }
}

What’s happening here

Let’s break down each piece:

ComponentActivity and setContent

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // This lambda is your Compose UI tree
        }
    }
}
Piece What it does
ComponentActivity Base activity that supports Compose
onCreate Called when the activity starts
setContent { } Replaces setContentView(R.layout.xml) — defines your UI as composables

@Composable functions

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello, $name!",
        modifier = modifier
    )
}
Piece What it does
@Composable Marks this as a Compose UI function (like a reusable component)
name parameter Data passed into the composable (like a prop)
modifier parameter Customizes layout, padding, click handling — always include this
Text() A built-in composable that renders text on screen

Modifier

Modifiers are how you configure composables — padding, size, click handlers, borders, etc:

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

Order matters. Modifier.padding(16.dp).fillMaxWidth() pads inside the width, while Modifier.fillMaxWidth().padding(16.dp) pads outside.

MaterialTheme

Wraps your UI in Material Design theming (colors, typography, shapes):

MaterialTheme {
    Greeting(name = "World")
}

Without it, Text still works but uses default system styling. With it, Text picks up your app’s Material theme.

Scaffold

Provides the basic Material layout structure — top bar, bottom bar, floating action button, and a content area:

Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
    Greeting(
        name = "World",
        modifier = Modifier.padding(innerPadding)
    )
}

innerPadding ensures your content doesn’t overlap the status bar or navigation bar.

@Preview

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MaterialTheme {
        Greeting(name = "World")
    }
}

Previews render in Android Studio’s design view without running the app. They must be:

  • Top-level functions (not nested)
  • Parameterless (or use @PreviewParameter)
  • Wrapped in a theme for realistic rendering

A more interactive Hello World

Let’s add a button and some state to make it interactive:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    HelloWorld(modifier = Modifier.padding(innerPadding))
                }
            }
        }
    }
}

@Composable
fun HelloWorld(modifier: Modifier = Modifier) {
    var name by remember { mutableStateOf("") }

    Column(
        modifier = modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Text(
            text = if (name.isBlank()) "Hello, World!" else "Hello, $name!",
            style = MaterialTheme.typography.headlineMedium
        )

        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Enter your name") },
            singleLine = true
        )
    }
}

What’s new here

Concept Explanation
remember Saves state across recompositions (but not configuration changes)
mutableStateOf Creates observable state — when name changes, Compose re-renders
by delegation Lets you read/write name as a plain String instead of name.value
Column Lays out children vertically with optional spacing
OutlinedTextField Material Design text input

Running the app

# Build and install on a connected device or emulator
./gradlew installDebug

# Or just build
./gradlew assembleDebug

In Android Studio, click Run (green play button) or press Shift+F10.

Common problems

Problem Solution
“Compose not found” Make sure buildFeatures { compose = true } is in your app-level Gradle
“Unresolved reference: setContent” Add implementation("androidx.activity:activity-compose:1.9.0")
“Unresolved reference: MaterialTheme” Add implementation("androidx.compose.material3:material3") and use the Compose BOM
App crashes on launch Check AndroidManifest.xml — your activity must be declared with MAIN/LAUNCHER intent filter
Preview not showing Make sure the @Preview function is top-level, not inside a class
“by remember” syntax error Import androidx.compose.runtime.getValue and setValue (Kotlin property delegation)

Complete minimal file list

If you want the absolute smallest working Compose app, here are the only files that matter:

settings.gradle.kts

pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

rootProject.name = "HelloCompose"
include(":app")

app/build.gradle.kts — as shown in Step 2

app/src/main/AndroidManifest.xml — as shown in Step 3

app/src/main/java/com/example/hello/MainActivity.kt — as shown in Step 4

That’s it. Four files and you have a running Compose app.

Next steps

Now that you have Hello World working: