La anotación @Composable es un elemento fundamental de Jetpack Compose que convierte una función Kotlin ordinaria en un bloque de construcción declarativo de la interfaz de usuario. Sin esta anotación, es imposible crear cualquier pantalla en el desarrollo moderno de Android. Según Google Android Developers, 2026, más del 80% de los proyectos nuevos en Kotlin usan Compose para construir la UI, y @Composable es la anotación más utilizada en el ecosistema.
Puntos Clave
@Composable es una anotación del lenguaje Kotlin que marca una función como destinada a describir la interfaz de usuario en el framework Jetpack Compose. Cuando el compilador de Kotlin encuentra esta anotación, genera código adicional que permite que la función funcione en el contexto de composición — el sistema de gestión del árbol de UI.
La anotación @Composable fue presentada por Google en 2021 junto con la primera versión estable de Jetpack Compose 1.0. Antes de su aparición, el desarrollo de interfaces en Android se realizaba exclusivamente mediante marcado XML y el sistema View. @Composable cambió radicalmente el enfoque: en lugar de describir la UI en un archivo de marcado separado, el desarrollador escribe la interfaz directamente en Kotlin.
La principal diferencia entre @Composable y las funciones Kotlin ordinarias es la capacidad de leer y reaccionar a los cambios de estado. Cuando una variable que lee una función Composable cambia su valor, el sistema programa automáticamente un reinicio (recomposición) de esa función. Esto libera al desarrollador de actualizar manualmente la UI mediante findViewById y setText.
La mecánica interna de @Composable se basa en el concepto de slot — un área de memoria especial que se asigna a cada función dentro de la composición. Este slot almacena los valores pasados a la función, así como la información de servicio necesaria para la comparación en llamadas posteriores.
Para declarar una función Composable, basta con añadir la anotación @Composable antes de la palabra clave fun. La función debe estar en un paquete que importe la anotación desde androidx.compose.runtime. Se recomienda escribir el nombre de la función con mayúscula inicial — esta es una convención ampliamente aceptada en la comunidad de Compose que distingue visualmente los componentes de UI de las funciones ordinarias.
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("¡Hola, $name!")
Button(onClick = { count++ }) {
Text("Pulsado $count veces")
}
}
}
Los parámetros de una función Composable pueden ser cualquier cosa — tipos primitivos, cadenas, lambdas e incluso otras funciones Composable pasadas a través de Slot API. Se recomienda hacer los parámetros inmutables (val) para evitar efectos secundarios durante la recomposición. Todos los datos mutables deben gestionarse a través de los mecanismos de estado de Compose.
Las funciones Composable no pueden devolver valores arbitrarios como las funciones regulares — su única tarea es construir o actualizar un fragmento del árbol de UI. Sin embargo, existen patrones especiales como State Hoisting, donde una función Compose acepta estado y callbacks a través de parámetros, permaneciendo pura y reutilizable.
El sistema Compose impone varias restricciones estrictas sobre cómo deben verse y comportarse las funciones Composable. La primera regla: una función Composable solo puede llamar a otras funciones Composable o funciones regulares que no tengan efectos secundarios. Esto garantiza la previsibilidad de la composición y el correcto funcionamiento de las optimizaciones de Compose.
La segunda regla se refiere al orden de ejecución. Compose tiene derecho a llamar a las funciones Composable en cualquier orden, por lo que el código en el cuerpo de dicha función no debe depender de la secuencia de llamada de las funciones vecinas. Cada función Composable debe ser autosuficiente al nivel de su posición en el árbol de UI.
La tercera regla — prohibición de efectos secundarios dentro del cuerpo de una función Composable. Operaciones como escribir en una base de datos, enviar solicitudes de red o modificar variables externas solo deben realizarse dentro de efectos especiales: LaunchedEffect, DisposableEffect o SideEffect. Violar esta regla conduce a un comportamiento impredecible durante las recomposiciones.
La cuarta regla: las funciones Composable deben ser idempotentes. Llamarlas de nuevo con los mismos argumentos debe producir la misma UI. Este requisito es necesario para el correcto funcionamiento de la optimización skipping, donde Compose omite redibujar funciones cuyos datos de entrada no han cambiado.
// Correcto: función Composable pura sin efectos secundarios
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Incorrecto: efecto secundario dentro del cuerpo
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // NO PERMITIDO
Text("Cargando...")
}
Veamos un ejemplo práctico de creación de una pantalla de perfil usando la anotación @Composable. Aquí demostramos la combinación de múltiples funciones Composable, el trabajo con estado y modificadores — elementos clave de cualquier diseño Compose.
@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 = "Usuario #$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)
}
}
En el ejemplo, cada función Composable es responsable de su parte de la pantalla: ProfileScreen gestiona el estado general y la composición de las funciones hijas, ProfileHeader muestra el avatar y el nombre, y StatsRow muestra un bloque de estadísticas. Este enfoque sigue el principio de responsabilidad única y simplifica la reutilización de componentes.
En Jetpack Compose, existen tres tipos principales de funciones Composable. El primer tipo — contenedores (Row, Column, Box, LazyColumn) — determina la disposición de los elementos hijos. El segundo tipo — elementos de visualización (Text, Image, Icon, Button) — renderizan componentes de UI específicos. El tercer tipo — funciones Composable personalizadas — combinan componentes integrados en bloques reutilizables.
Los contenedores se diferencian de los elementos regulares en que aceptan una lambda content — el último parámetro de tipo @Composable () -> Unit. Este mecanismo permite construir árboles de UI anidados: cada contenedor genera una composición hija con su propio contexto y área de memoria.
Las funciones Composable personalizadas se dividen en dos subtipos: inteligentes (smart) y tontas (dumb). Las funciones inteligentes gestionan el estado y la lógica — contienen llamadas a remember, LaunchedEffect y otras API de Compose. Las funciones tontas reciben todos los datos a través de parámetros y solo los muestran. La separación en componentes inteligentes y tontos mejora la testabilidad y la reutilización del código.
| Tipo | Ejemplo | Propósito |
|---|---|---|
| Contenedor | Column, Row, Box | Gestión de la disposición de elementos hijos |
| Elemento | Text, Image, Button | Visualización de contenido y manejo de entrada |
| Personalizado | ProfileCard, UserList | Combinación de componentes estándar |
La principal ventaja de la anotación @Composable es la capacidad de crear componentes de UI reutilizables sin herencia ni jerarquías complejas de clases. A diferencia del sistema View, donde cada elemento personalizado requería crear una clase Java con constructores, un componente Composable es simplemente una función Kotlin con parámetros.
Para garantizar la reutilización, se utiliza el patrón Slot API, donde una función Composable acepta lambdas content para diferentes áreas de su diseño. Por ejemplo, un componente Card puede aceptar content separado para el encabezado, el cuerpo y el pie, lo que lo hace universal para cualquier pantalla de la aplicación.
Los modificadores (Modifier) juegan un papel clave en la reutilización: permiten configurar paddings, tamaños, clics y animaciones sin cambiar el componente en sí. Se recomienda pasar siempre Modifier como parámetro de una función Composable con un valor por defecto: Modifier = Modifier — esta es una práctica estándar adoptada en las bibliotecas oficiales de Google.
@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()
}
}
}
Gracias a Slot API, el componente SectionCard se puede usar en diferentes pantallas con diferentes contenidos — formularios, listas, bloques de texto. La combinación de modificadores y Slot API hace que los componentes Compose sean extremadamente flexibles sin perder la seguridad de tipos que proporciona Kotlin.
Preguntas Frecuentes
Una función @Composable se ejecuta en el contexto de composición y puede leer el estado, reiniciándose automáticamente cuando este cambia. Las funciones Kotlin ordinarias no tienen acceso a los mecanismos de seguimiento de estado ni participan en la construcción del árbol de UI.
No, las funciones Composable solo se pueden llamar desde otras funciones Composable, ya que se requiere un contexto de composición especial. Para integrar código Compose con Kotlin normal, se utiliza el método setContent { } en Activity o ComposeView en el sistema View.
Esta es una convención de nomenclatura adoptada en la comunidad de Compose. La mayúscula inicial distingue visualmente los componentes de UI de las funciones ordinarias, siguiendo las reglas de nomenclatura de clases. No es un requisito del compilador, sino una práctica recomendada en la documentación de Google.
No hay límites en la cantidad. En la práctica, una pantalla grande puede contener 50–100 funciones Composable, incluyendo componentes integrados (Text, Button) y personalizados. Compose optimiza el árbol de funciones y solo ejecuta aquellas cuyos datos de entrada han cambiado.
Normalmente las funciones Composable devuelven Unit, ya que su tarea es construir la UI. Sin embargo, existen funciones especializadas como remember y derivedStateOf que están marcadas con @Composable y devuelven valores. Esta es una excepción, no la regla.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también