Estabilidad en Compose

Compose considera que los tipos son estables o inestables. Un tipo es estable si es inmutable, o si es posible que Compose sepa si su valor cambió entre recomposiciones. Un tipo es inestable si Compose no puede saber si su valor cambió entre recomposiciones.

Compose usa la estabilidad de los parámetros de un elemento componible para determinar si puede omitirlo durante la recomposición:

  • Parámetros estables: Si un elemento componible tiene parámetros estables que no cambiaron, Compose lo omite.
  • Parámetros inestables: Si un elemento componible tiene parámetros inestables, Compose siempre lo vuelve a componer cuando vuelve a componer el elemento superior del componente.

Si tu app incluye muchos componentes inestables innecesariamente que Compose siempre recompone, es posible que observes problemas de rendimiento, entre otros.

En este documento, se detalla cómo puedes aumentar la estabilidad de tu app para mejorar el rendimiento y la experiencia del usuario en general.

Objetos inmutables

En los siguientes fragmentos, se demuestran los principios generales de la estabilidad y la recomposición.

La clase Contact es una clase de datos inmutable. Esto se debe a que todos sus parámetros son primitivos definidos con la palabra clave val. Una vez que creas una instancia de Contact, no puedes cambiar el valor de las propiedades del objeto. Si intentas hacerlo, tendrás que crear un objeto nuevo.

data class Contact(val name: String, val number: String)

El elemento componible ContactRow tiene un parámetro de tipo Contact.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

Considera lo que sucede cuando el usuario hace clic en el botón de activación y cambia el estado selected:

  1. Compose evalúa si debe recomponer el código dentro de ContactRow.
  2. Ve que el único argumento para ContactDetails es de tipo Contact.
  3. Como Contact es una clase de datos inmutable, Compose está seguro de que ninguno de los argumentos para ContactDetails cambió.
  4. Por lo tanto, Compose omite ContactDetails y no lo vuelve a componer.
  5. Por otro lado, los argumentos para ToggleButton cambiaron y Compose vuelve a componer ese componente.

Objetos mutables

Si bien en el ejemplo anterior se usa un objeto inmutable, es posible crear un objeto mutable. Considera el siguiente fragmento:

data class Contact(var name: String, var number: String)

Como cada parámetro de Contact ahora es var, la clase ya no es inmutable. Si se modificaran sus propiedades, Compose no lo detectaría. Esto se debe a que Compose solo hace un seguimiento de los cambios en los objetos de estado de Compose.

Compose considera que esta clase es inestable. Compose no omite la recomposición de las clases inestables. Por lo tanto, si Contact se definiera de esta manera, ContactRow en el ejemplo anterior se recompondría cada vez que cambia selected.

Implementación en Compose

Puede ser útil, aunque no fundamental, considerar cómo Compose determina exactamente qué funciones omitir durante la recomposición.

Cuando el compilador de Compose se ejecuta en tu código, marca cada función y tipo con una de varias etiquetas. Estas etiquetas reflejan cómo Compose controla la función o el tipo durante la recomposición.

Funciones

Compose puede marcar funciones como skippable o restartable. Ten en cuenta que puede marcar una función como uno, ambos o ninguna de las siguientes:

  • Se puede omitir: Si el compilador marca un elemento componible como que se puede omitir, Compose puede omitirlo durante la recomposición si todos sus argumentos son iguales a sus valores anteriores.
  • Reiniciable: Un elemento componible que se puede reiniciar funciona como un "alcance" en el que puede comenzar la recomposición. En otras palabras, la función puede ser un punto de entrada para el que Compose puede comenzar a volver a ejecutar el código para la recomposición después de los cambios de estado.

Tipos

Compose marca los tipos como inmutables o estables. Cada tipo es uno u otro:

  • Inmutable: Compose marca un tipo como inmutable si el valor de sus propiedades nunca puede cambiar y todos los métodos son transparentes de referencia.
    • Ten en cuenta que todos los tipos primitivos se marcan como inmutables. Entre ellos, se incluyen String, Int y Float.
  • Estable: Indica un tipo cuyas propiedades pueden cambiar después de la construcción. Si esas propiedades cambian durante el tiempo de ejecución, Compose se dará cuenta de esos cambios.

Estabilidad de depuración

Si la app vuelve a componer un elemento componible cuyos parámetros no cambiaron, primero verifica su definición para los parámetros que sean claramente mutables. Compose siempre recompone un componente si pasas un tipo con propiedades var o una propiedad val que usa un tipo inestable conocido.

Si deseas obtener información detallada para diagnosticar problemas complejos de estabilidad en Compose, consulta la guía Estabilidad de depuración.

Corrige los problemas de estabilidad

Si deseas obtener información para aportar estabilidad a tu implementación de Compose, consulta la guía Cómo corregir problemas de estabilidad.

Resumen

En general, debes tener en cuenta los siguientes puntos:

  • Parámetros: Compose determina la estabilidad de cada parámetro de los elementos componibles para determinar cuáles debe omitir durante la recomposición.
  • Correcciones inmediatas: Si notas que no se omite el elemento componible y esto causa un problema de rendimiento, primero debes verificar las causas evidentes de la inestabilidad, como los parámetros var.
  • Informes del compilador: Puedes usar los informes del compilador para determinar la estabilidad que se infiere de tus clases.
  • Colecciones: Compose siempre considera inestables las clases de colección, como List, Set y Map. Esto se debe a que no se puede garantizar que sean inmutables. En su lugar, puedes usar colecciones inmutables de Kotlinx o anotar tus clases como @Immutable o @Stable.
  • Otros módulos: Compose siempre considera que son inestables en los casos en que se encuentran de módulos en los que no se ejecuta el compilador de Compose. Une las clases en clases de modelos de IU si es necesario.

Lecturas adicionales