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.
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
| Dependency | What it provides |
|---|---|
androidx.compose.ui:ui | The core Compose runtime and UI primitives — layout, drawing, input, the Modifier system |
androidx.compose.ui:ui-graphics | Graphics primitives (colors, brushes, paths) used across Compose UI |
androidx.compose.ui:ui-tooling-preview | The 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:material3 | Material Design 3 components — Button, TextField, Scaffold, theming |
androidx.activity:activity-compose | The 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.