Recomposition — qué es, reconstrucción de la UI al cambiar el estado

Autor: IT Sectr Publicado: 2026-06-27 Tiempo de lectura: 7 min

Recomposition es un mecanismo de Jetpack Compose que reconstruye automáticamente partes de la interfaz de usuario cuando los datos cambian, sin necesidad de actualizar manualmente los elementos View. Cuando una variable de estado de la que depende una función Composable cambia su valor, Compose reinicia solo esa función, dejando el resto del árbol UI intacto. Según Google Android Developers, 2026, comprender correctamente Recomposition permite reducir los redibujados innecesarios en un 40–60%.

Puntos Clave

  • Recomposition — reinicio de funciones Composable cuando cambian sus datos de entrada o Estado
  • Skipping — omisión de funciones cuyos parámetros no han cambiado (comparación por equals)
  • Stability determina si Compose puede omitir una función — los tipos estables se comparan correctamente
  • Smart Recomposition reinicia solo el conjunto mínimo de funciones, no todo el árbol
  • La recomposición no garantiza el redibujado — Layout y Drawing pueden omitir su fase

Qué es Recomposition en Jetpack Compose

Recomposition es la re-ejecución de funciones Composable que ya han participado en Composition, con nuevos valores de parámetros o estado. El objetivo principal de la recomposición es sincronizar el árbol UI con los datos actuales sin reconstruir toda la interfaz desde cero. A diferencia de Composition, que ocurre una sola vez, Recomposition puede activarse cientos de veces durante la vida útil de una pantalla.

Recomposition funciona según el principio de smart invalidation: Compose rastrea qué objetos State lee cada función Composable y marca para reinicio solo aquellas cuyas dependencias han cambiado. Esto se logra mediante un sistema de snapshot que registra todas las operaciones de lectura de State durante la ejecución, y un Composer que asigna estas dependencias a funciones específicas.

Es importante entender: la recomposición no significa redibujado inmediato de la pantalla. Compose trabaja en tres fases: Composition (construcción de la descripción UI), Layout (cálculo de tamaños y posiciones) y Drawing (renderizado en el canvas). Si después de la recomposición los tamaños y posiciones de los elementos no han cambiado, la fase Layout se puede omitir. Si la apariencia visual no ha cambiado — se omite Drawing. Esta arquitectura de tres fases garantiza un costo mínimo para cada actualización de UI.

Disparadores de recomposición: qué causa el reinicio de funciones

Existen tres disparadores principales de recomposición. El primero es un cambio en un objeto State leído dentro del cuerpo de una función Composable. Cuando mutableStateOf o derivedStateOf cambia su valor, todas las funciones que registraron la lectura de este State en la composición anterior se marcan para reinicio.

El segundo disparador es un cambio de parámetro de una función Composable cuando es llamada desde una función padre. Si la función padre pasa un nuevo valor (por ejemplo, el texto o número cambió), la función hija se reiniciará, incluso si no lee State internamente. Compose compara los valores nuevos y antiguos de los parámetros mediante equals, y si son iguales — la función puede omitirse.

El tercer disparador es un cambio de CompositionLocal a través de CompositionLocalProvider. Todas las funciones que leen CompositionLocal mediante .current se reinician cuando el proveedor cambia. Este mecanismo es utilizado por MaterialTheme: cambiar de tema (claro/oscuro) provoca la recomposición de todos los componentes que leen MaterialTheme.colorScheme.

kotlin
@Composable
fun RecompositionDemo() {
    var counter by remember { mutableStateOf(0) }
    var text by remember { mutableStateOf("Hello") }

    Column {
        Text("Counter: $counter")  // recomposition when counter changes
        Text("Message: $text")    // recomposition when text changes

        Button(onClick = { counter++ }) {
            Text("+1")
        }
        Button(onClick = { text = "World" }) {
            Text("Change Text")
        }
    }
}

Al hacer clic en el botón +1, counter cambia, provocando la recomposición solo de la primera línea Text y de la propia Column. La segunda línea Text que muestra text no se reinicia. Este aislamiento es el resultado del sistema de snapshot: cada función Composable solo conoce los objetos State que ha leído.

Optimización de la recomposición: técnicas prácticas

La optimización de la recomposición comienza con la elección de las estructuras de datos adecuadas. Use colecciones inmutables (listOf, mapOf) en lugar de mutables (mutableListOf). Compose compara parámetros mediante equals, y si una colección ha cambiado pero equals devuelve true — la función no se reiniciará. Para colecciones mutables, use SnapshotStateList, que implementa un seguimiento correcto de cambios a nivel de elementos.

La segunda técnica es extraer partes estables de la UI en funciones Composable separadas. Si una parte de la pantalla no depende de un estado que cambia con frecuencia, extráigala en una función separada con parámetros. Cuando ocurre la recomposición, la función estable recibe los mismos parámetros, Compose los compara y omite la ejecución. Esto es más eficiente que reiniciar esa parte como parte de una función grande donde algunos parámetros han cambiado.

La tercera técnica son las claves en LazyColumn. Siempre especifique una clave para los elementos en LazyColumn, LazyGrid y otros contenedores perezosos. La clave permite a Compose identificar elementos cuando la lista cambia: al añadir, eliminar o reordenar. Sin una clave, Compose reinicia todos los elementos de la lista ante cualquier cambio, lo que en listas grandes causa una degradación notable del rendimiento.

kotlin
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
    Column {
        Header()                         // does not depend on items — no recomposition
        Spacer(modifier = Modifier.height(8.dp))
        LazyColumn {
            items(items, key = { it.id }) { item ->
                ItemRow(item = item)   // recomposition only for changed items
            }
        }
    }
}

@Composable
fun Header() {
    Text("Item list", style = MaterialTheme.typography.headlineMedium)
}

@Composable
fun ItemRow(item: Item) {
    Text(item.title)
}

Skipping y Stability en Compose

Skipping es un mecanismo mediante el cual Compose omite la ejecución de una función Composable si todos sus parámetros no han cambiado. Para que skipping funcione correctamente, los tipos de los parámetros deben ser estables. El compilador de Kotlin marca como estables: tipos primitivos (Int, Float, Boolean), String, funciones lambda, y clases cuyos campos son todos estables y val.

Stability es la anotación @Stable o @Immutable que se puede agregar a clases de datos personalizadas. Si una clase contiene un campo mutable (var), el compilador la considera inestable, y Compose no podrá omitir funciones con tales parámetros. Para clases con var, use @Stable si garantiza que la notificación de cambio se enviará a través del sistema de snapshot.

Puede verificar la estabilidad usando el indicador del compilador -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Genera un informe con una lista de todas las funciones Composable y sus parámetros indicando la estabilidad. Si un parámetro es inestable, el skipping es imposible para esa función y se reiniciará en cada recomposición del padre.

TipoEstabilidadSkipping
Int, Float, BooleanEstable
StringEstable
LambdaEstable
data class con campos valEstable
data class con campos varInestableNo
List<String>InestableNo

Nota: List<String> se considera inestable porque es una interfaz, no una implementación concreta. Use immutableListOf() de la biblioteca Kotlin Collections Immutable o envuelva la lista en una clase @Stable. Lambda siempre es estable porque su equals solo compara referencias, y cuando se crea una nueva lambda en el lugar de la llamada, la función padre también se reinicia.

Monitoreo de recomposición en Android Studio

Para monitorear la recomposición, Android Studio proporciona el Layout Inspector con el modo Compose Recomposition Counts. En este modo, cada función Composable muestra el número de recomposiciones y las razones del reinicio. Esto permite encontrar rápidamente funciones que se recomponen con demasiada frecuencia y determinar la causa raíz — parámetros inestables o dependencias de State innecesarias.

Herramientas adicionales: Compose Metrics (recopilación de estadísticas mediante pruebas de instrumentación) y Recomposition Timer (medición del tiempo de ejecución de cada función). Google recomienda habilitar estas herramientas durante la creación de perfiles y deshabilitarlas en las compilaciones de lanzamiento, ya que añaden hasta un 20% de sobrecarga por recomposición.

Al analizar recomposiciones, busque patrones de recomposición innecesaria: una función se reinicia aunque su UI de salida no debería cambiar. Una causa común es el uso de lambdas sin remember, donde se crea un nuevo objeto lambda cada vez y Compose considera que el parámetro ha cambiado. Solución: envuelva las lambdas en remember { } con capturas fijas.

kotlin
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
    Child(onClick = { doSomething() })  // new lambda every time
}

// Good: remember stabilizes the lambda
@Composable
fun Parent() {
    val onClick = remember { { doSomething() } }
    Child(onClick = onClick)  // same reference
}

Preguntas Frecuentes

¿La recomposición significa redibujado de la pantalla?

No, la recomposición es solo la fase de Composition. Después de ella se ejecutan Layout y Drawing. Si después de la recomposición los tamaños y posiciones de los elementos no han cambiado, Layout y Drawing pueden omitirse por completo, ahorrando recursos de GPU.

¿Con qué frecuencia puede ocurrir la recomposición?

Durante las animaciones, la recomposición puede ejecutarse hasta 120 veces por segundo (120fps). Para la interacción normal — 10–60 veces por segundo. Es importante que cada recomposición quepa dentro del presupuesto de fotograma (8–16 ms), de lo contrario la aplicación se ralentizará.

¿Por qué una función se recomponer aunque el Estado no ha cambiado?

La razón es un cambio de parámetro de la función padre. El padre se reinicia (por su propia razón) y pasa un nuevo valor. Para evitarlo, verifique la estabilidad de los parámetros y use remember para estabilizar lambdas y valores calculados.

¿Se puede deshabilitar la recomposición para una función específica?

No hay una desactivación directa, pero existe el skipping forzado mediante readInComposition — State se lee fuera del cuerpo de la función, lo que no registra una dependencia. Úselo con precaución: la función no reaccionará a los cambios, lo que puede llevar a una UI obsoleta.

¿Qué es más costoso: Composition o Recomposition?

Composition es más costosa porque crea todos los slots y nodos del árbol desde cero. Recomposition reutiliza los slots existentes y solo actualiza sus valores. En la práctica, Composition de una pantalla toma 2–10 ms, mientras que la recomposición de un solo elemento toma 0.1–1 ms.

Resumen

  • Recomposition — reinicio selectivo de funciones Composable cuando cambian su Estado o parámetros
  • Sistema de snapshot rastrea las dependencias de las funciones del Estado y programa la recomposición
  • Skipping solo es posible para funciones con parámetros estables (@Stable o immutable)
  • Tres disparadores de recomposición: cambio de Estado, cambio de parámetros, cambio de CompositionLocal
  • List<T> se considera inestable — use colecciones inmutables para un skipping correcto
  • Layout Inspector muestra contadores de recomposición para cada función
  • Recomendación: extraiga partes estables de UI en funciones separadas y use remember para lambdas

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.

Discutir el proyecto

Lea también