The @Composable annotation is a fundamental element of Jetpack Compose that turns an ordinary Kotlin function into a declarative building block of the user interface. Without this annotation, it is impossible to create any screen in modern Android development. According to Google Android Developers, 2026, more than 80% of new Kotlin projects use Compose for building UI, and @Composable is the most frequently used annotation in the ecosystem.
Key Takeaways
@Composable is a Kotlin language annotation that marks a function as intended for describing the user interface in the Jetpack Compose framework. When the Kotlin compiler encounters this annotation, it generates additional code that allows the function to work within the composition context — the UI tree management system.
The @Composable annotation was introduced by Google in 2021 along with the first stable version of Jetpack Compose 1.0. Before its appearance, Android interface development was done exclusively through XML markup and the View system. @Composable radically changed the approach: instead of describing UI in a separate markup file, the developer writes the interface directly in Kotlin.
The main difference between @Composable and ordinary Kotlin functions is the ability to read and react to state changes. When a variable that a Composable function reads changes its value, the system automatically schedules a restart (recomposition) of that function. This frees the developer from manually updating UI through findViewById and setText.
The internal mechanics of @Composable relies on the slot concept — a special memory area allocated for each function within the composition. This slot stores values passed to the function as well as metadata needed for comparison during subsequent calls.
To declare a Composable function, simply add the @Composable annotation before the fun keyword. The function must be in a package that imports the annotation from androidx.compose.runtime. It is recommended to write the function name starting with a capital letter — this is a widely accepted convention in the Compose community that visually distinguishes UI components from ordinary functions.
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("Hello, $name!")
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
}
The parameters of a Composable function can be anything — primitive types, strings, lambdas, and even other Composable functions passed through the Slot API. It is recommended to make parameters immutable (val) to avoid side effects during recomposition. All mutable data should be managed through Compose state mechanisms.
Composable functions cannot return arbitrary values like regular functions — their only task is to build or update a fragment of the UI tree. However, there are special patterns like State Hoisting, where a Compose function accepts state and callbacks through parameters, remaining pure and reusable.
The Compose system imposes several strict constraints on how Composable functions should look and behave. The first rule: a Composable function can only call other Composable functions or regular functions that have no side effects. This ensures predictability of composition and correct operation of Compose optimizations.
The second rule concerns execution order. Compose has the right to call Composable functions in any order, so the code in the body of such a function must not rely on the call sequence of neighboring functions. Each Composable function must be self-sufficient at the level of its position in the UI tree.
The third rule — prohibition of side effects inside the body of a Composable function. Operations like writing to a database, sending network requests, or modifying external variables must only be performed inside special effects: LaunchedEffect, DisposableEffect, or SideEffect. Violating this rule leads to unpredictable behavior during recompositions.
The fourth rule: Composable functions must be idempotent. Calling them again with the same arguments should produce the same UI. This requirement is necessary for correct skipping optimization, where Compose skips redrawing functions whose input data has not changed.
// Correct: pure Composable function without side effects
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Wrong: side effect inside the body
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // NOT ALLOWED
Text("Loading...")
}
Let us look at a practical example of creating a profile screen using the @Composable annotation. Here we demonstrate combining multiple Composable functions, working with state and modifiers — key elements of any Compose layout.
@Composable
fun ProfileScreen(userId: String) {
var isFollowed by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
ProfileHeader(userId = userId)
Spacer(modifier = Modifier.height(16.dp))
StatsRow(posts = 42, followers = 1280)
Spacer(modifier = Modifier.height(24.dp))
FollowButton(
isFollowed = isFollowed,
onToggle = { isFollowed = !isFollowed }
)
}
}
@Composable
fun ProfileHeader(userId: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = "https://example.com/avatars/$userId",
contentDescription = "User avatar")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "User #$userId", style = MaterialTheme.typography.headlineMedium)
}
}
@Composable
fun StatsRow(posts: Int, followers: Int) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
StatItem("Posts", posts)
StatItem("Followers", followers)
}
}
@Composable
fun StatItem(label: String, value: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$value", style = MaterialTheme.typography.headlineSmall)
Text(text = label, style = MaterialTheme.typography.bodySmall)
}
}
In the example, each Composable function is responsible for its own part of the screen: ProfileScreen manages the overall state and composition of child functions, ProfileHeader displays the avatar and name, and StatsRow shows a statistics block. This approach follows the single responsibility principle and simplifies component reuse.
In Jetpack Compose, there are three main types of Composable functions. The first type — containers (Row, Column, Box, LazyColumn) — determines the layout of child elements. The second type — display elements (Text, Image, Icon, Button) — render specific UI components. The third type — custom Composable functions — combine built-in components into reusable blocks.
Containers differ from regular elements in that they accept a content lambda — the last parameter of type @Composable () -> Unit. This mechanism allows building nested UI trees: each container spawns a child composition with its own context and memory area.
Custom Composable functions are divided into two subtypes: smart and dumb. Smart functions manage state and logic — they contain calls to remember, LaunchedEffect, and other Compose APIs. Dumb functions receive all data through parameters and only display it. Separating smart and dumb components improves testability and code reuse.
| Type | Example | Purpose |
|---|---|---|
| Container | Column, Row, Box | Managing the layout of child elements |
| Element | Text, Image, Button | Displaying content and handling input |
| Custom | ProfileCard, UserList | Combination of standard components |
The main advantage of the @Composable annotation is the ability to create reusable UI components without inheritance and complex class hierarchies. Unlike the View system, where each custom element required creating a Java class with constructors, a Composable component is simply a Kotlin function with parameters.
To ensure reusability, the Slot API pattern is used, where a Composable function accepts content lambdas for different areas of its layout. For example, a Card component can accept separate content for the header, body, and footer, making it universal for any application screen.
Modifiers play a key role in reusability: they allow configuring padding, sizes, clicks, and animations without changing the component itself. It is recommended to always pass Modifier as a parameter of a Composable function with a default value: Modifier = Modifier — this is a standard practice adopted in official Google libraries.
@Composable
fun SectionCard(
modifier: Modifier = Modifier,
title: String,
content: @Composable () -> Unit
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
content()
}
}
}
Thanks to the Slot API, the SectionCard component can be used on different screens with different content — forms, lists, text blocks. Combining modifiers and the Slot API makes Compose components extremely flexible without losing the type safety that Kotlin provides.
Frequently Asked Questions
A @Composable function executes in the composition context and can read state, automatically restarting when it changes. Ordinary Kotlin functions do not have access to state tracking mechanisms and do not participate in building the UI tree.
No, Composable functions can only be called from other Composable functions because a special composition context is required. To integrate Compose code with regular Kotlin, the setContent { } method on Activity or ComposeView in the View system is used.
This is a naming convention adopted in the Compose community. A capital letter visually distinguishes UI components from ordinary functions, following class naming rules. It is not a compiler requirement, but a recommended practice in Google documentation.
There are no limits on the number. In practice, a large screen can contain 50–100 Composable functions, including built-in components (Text, Button) and custom ones. Compose optimizes the function tree and only executes those whose input data has changed.
Usually Composable functions return Unit, since their task is to build UI. However, there are specialized functions like remember and derivedStateOf that are marked @Composable and return values. This is an exception, not the rule.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also