Setup & Compose BOM

Before RecipeBox can show a single composable on screen, the project needs to know Compose exists. That means three things: turning on Compose as a Gradle build feature, pulling in the right dependencies through the Compose BOM, and wiring an Activity to actually host composable content. This lesson walks through all three, module by module.

Enabling Compose in Gradle

Compose support is opt-in per module. In the app module's build.gradle.kts, you turn on the compose build feature and point the Kotlin compiler at the Compose compiler extension:

android {
    namespace = "com.recipebox.app"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.recipebox.app"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }

    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.14"
    }
}

buildFeatures { compose = true } is the switch that tells the Android Gradle plugin this module contains composable code at all. composeOptions.kotlinCompilerExtensionVersion pins the version of the Compose compiler plugin — the piece that rewrites your @Composable functions at compile time so they can participate in recomposition. This version has to be compatible with the Kotlin version the module uses; recent Compose releases moved this pairing into a dedicated Gradle plugin so you often don't need to set it by hand at all, but it's worth understanding what this line does even when a template fills it in for you.

The Compose BOM: One Version Number Instead of Many

Jetpack Compose isn't one library — it's a family of libraries (ui, foundation, material3, animation, and more), each versioned and released independently. Picking a compatible combination of versions by hand is fiddly and easy to get wrong. The Compose BOM (Bill of Materials) solves this: you import one BOM version, and every Compose library you then declare without a version number automatically resolves to the version that BOM has verified works correctly together.

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

    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.9.0")

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

Notice that ui, ui-graphics, ui-tooling-preview, and material3 all have no version string — the platform(...) BOM import supplies it. If RecipeBox later needs another Compose artifact (say, compose-animation for custom transitions), you add the line with no version and trust the BOM to pick one that's tested against everything else already in the list. Bumping Compose later becomes a one-line change: update the BOM version, and every dependent artifact moves together.

Note: activity-compose is versioned outside the BOM (it lives in the AndroidX Activity library family, not the Compose family), so it keeps its own version number even in this setup. This is a common point of confusion the first time you set up a Compose module.

What's in Each Core Dependency

DependencyWhat it provides
androidx.compose.ui:uiThe core Compose runtime and UI primitives — layout, drawing, input, the Modifier system
androidx.compose.ui:ui-graphicsGraphics primitives (colors, brushes, paths) used across Compose UI
androidx.compose.ui:ui-tooling-previewThe lightweight annotations (like @Preview) needed at compile time, without pulling in the full Android Studio tooling
androidx.compose.ui:ui-tooling (debug only)The actual preview-rendering machinery Android Studio uses — kept out of release builds via debugImplementation
androidx.compose.material3:material3Material Design 3 components — Button, TextField, Scaffold, theming
androidx.activity:activity-composeThe bridge between a Compose UI tree and a regular Android Activity — supplies setContent { }

ComponentActivity + setContent: The Entry Point

A Compose screen still needs somewhere to live, and that's an ordinary Android Activity — specifically, one that extends ComponentActivity (or one of its subclasses, like AppCompatActivity). Instead of calling setContentView(R.layout.activity_main) with an XML resource, you call setContent { } and pass it composable code directly:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RecipeBoxApp()
        }
    }
}

setContent { } is where the imperative Android world (an Activity going through its lifecycle) hands off to the declarative Compose world. Everything inside that lambda is composable code, and RecipeBoxApp() — the composable we'll write in the next lesson — becomes the root of the entire UI tree for this screen.

Next up: With the module configured and the entry point understood, Your First Composable writes and wires up an actual "Hello RecipeBox" screen end to end.