On this page
The @Composable annotation tells the Compose compiler plugin to transform your function so it can take part in composition: it can remember values, be re-run when state changes, and be skipped when nothing relevant changed. Writing good composables means respecting a few rules that ordinary functions don't have.
Anatomy of a well-written composable
@Composable
fun PriceTag(
pricePaise: Long, // 1. required data first
modifier: Modifier = Modifier, // 2. first optional parameter: modifier
discounted: Boolean = false, // 3. other optional parameters with defaults
onClick: (() -> Unit)? = null, // 4. events as lambdas
) {
val text = remember(pricePaise) { formatRupees(pricePaise) } // cache derived values
Surface(
modifier = modifier.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
color = if (discounted) MaterialTheme.colorScheme.tertiaryContainer else MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(8.dp),
) {
Text(text, Modifier.padding(horizontal = 8.dp, vertical = 4.dp), style = MaterialTheme.typography.labelLarge)
}
}- Named in PascalCase because it emits UI, like a noun:
PriceTag, notshowPriceTag. - Returns
Unit. It emits UI rather than returning a View. - Accepts a
modifieras the first optional parameter and applies it to the root element. This lets callers position and size it. - Data in, events out: state comes in as parameters, and user actions go out as lambdas.
The rules composables live by
@Composable
fun ArticleScreen(id: String, viewModel: ArticleViewModel) {
// โ Runs on every recomposition: duplicate analytics events and requests
analytics.log("article_viewed", id)
viewModel.load(id)
// โ
Runs when entering composition, and again only if id changes
LaunchedEffect(id) {
analytics.log("article_viewed", id)
viewModel.load(id)
}
}More on effect APIs in Side Effects in Compose.
Slot APIs: composables that accept composables
Instead of a long list of parameters for every variation, accept @Composable lambdas, called slots. Material's Scaffold, TopAppBar and ListItem work this way.
@Composable
fun SectionCard(
title: String,
modifier: Modifier = Modifier,
action: (@Composable () -> Unit)? = null, // optional slot
content: @Composable ColumnScope.() -> Unit, // trailing content slot with ColumnScope receiver
) {
Card(modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(title, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f))
action?.invoke()
}
Spacer(Modifier.height(8.dp))
content()
}
}
}
// Usage
SectionCard(title = "Recent orders", action = { TextButton(onClick = onSeeAll) { Text("See all") } }) {
orders.take(3).forEach { OrderRow(it) }
}Modifiers: order matters
Box(Modifier.padding(16.dp).background(Color.Yellow)) // padding outside: yellow area excludes padding
Box(Modifier.background(Color.Yellow).padding(16.dp)) // background first: yellow includes padding
Box(Modifier.clickable { }.padding(16.dp)) // whole padded area is clickable
Box(Modifier.padding(16.dp).clickable { }) // only the inner area is clickableEach modifier wraps the next, so read the chain from outside to inside.
Previews
@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Preview(name = "Large font", fontScale = 1.5f, showBackground = true)
@Composable
private fun PriceTagPreview() {
AppTheme { PriceTag(pricePaise = 129_900, discounted = true) }
}Previews work best with stateless composables that take plain data, which is another reason to hoist state out of reusable components.
Skipping and stability, briefly
Compose can skip a composable during recomposition if all its parameters are stable and equal to last time. Primitives, Strings, lambdas (in most cases) and data classes with stable val properties are stable. Classes with vars, or interfaces such as List that could be mutable, may be treated as unstable, although the Compose compiler's strong skipping mode, enabled by default in recent versions, relaxes this considerably. Deep dive: Compose Performance Optimization.
Designing a small component library
Design-system components
Wrap Material components (AppButton, AppTextField) to enforce your brand's defaults, and expose a modifier and slots.
Screen vs component
Screen-level composables connect to ViewModels. Components below them stay stateless and previewable.
Lists
Row composables take an item and click lambdas, and they never fetch data themselves.
Common mistakes
- Not exposing a
modifierparameter, or applying it to an inner element instead of the root. - Reading a ViewModel deep inside small components.
- Expensive computations in the body without
remember. - Mutating external variables from composable bodies.
Frequently asked questions
Can a composable return a value?
Composables that emit UI return Unit. Composables that compute values, such as remember or stringResource, return them and are named in camelCase.
Can I call a composable from a normal function?
No. Composables can only be called from other composables, because they need the composition context the compiler plugin adds.
Why does my composable run twice?
Recomposition can run it many times. That's normal. Make sure the body is cheap and free of side effects.
Conclusion
Composable functions are idempotent descriptions of UI that may run often, in any order, or be skipped. Design them with data in and events out, a modifier parameter, slots for flexibility, and no side effects in the body.
Next step: learn where state should live in State Management in Jetpack Compose.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Jetpack Compose clicks once you build real screens with real state. Practise modern Android UI development on projects that go beyond toy examples.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.