Cuando te encuentras con una clase inestable que causa problemas de rendimiento, debes hacerla estable. En este documento, se describen varias técnicas que puedes usar para hacerlo.
Habilitar la omisión fuerte
Primero, debes intentar habilitar el modo de omisión intensa. El modo de omisión estricto permite omitir los elementos componibles con parámetros inestables y es el método más fácil para solucionar problemas de rendimiento causados por la estabilidad.
Consulta Omisiones fuertes para obtener más información.
Haz que la clase sea inmutable
También puedes intentar hacer que una clase inestable sea completamente inmutable.
- Inmutable: Indica un tipo en el que el valor de cualquier propiedad nunca puede cambiar después de que se construye una instancia de ese tipo y todos los métodos son referencialmente transparentes.
- Asegúrate de que todas las propiedades de la clase sean
val
, en lugar devar
, y de tipos inmutables. - Los tipos primitivos, como
String, Int
yFloat
, siempre son inmutables. - Si esto es imposible, debes usar el estado de Compose para cualquier propiedad mutable.
- Asegúrate de que todas las propiedades de la clase sean
- Estable: Indica un tipo que es mutable. El entorno de ejecución de Compose no detecta si cualquiera de las propiedades públicas o el comportamiento del método del tipo generaría resultados diferentes a los de una invocación anterior, y cuándo lo hizo.
Colecciones inmutables
Un motivo común por el que Compose considera que una clase es inestable son las colecciones. Como se mencionó en la página Cómo diagnosticar problemas de estabilidad, el compilador de Compose no puede estar completamente seguro de que las colecciones como List, Map
y Set
sean realmente inmutables y, por lo tanto, las marca como inestables.
Para resolver esto, puedes usar colecciones inmutables. El compilador de Compose incluye compatibilidad con las colecciones inmutables de Kotlinx. Se garantiza que estas colecciones son inmutables, y el compilador de Compose las trata como tales. Esta biblioteca aún está en versión alfa, por lo que es posible que haya cambios en su API.
Considera nuevamente esta clase inestable de la guía Cómo diagnosticar problemas de estabilidad:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Puedes hacer que tags
sea estable con una colección inmutable. En la clase, cambia el tipo de tags
a ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Después de hacerlo, todos los parámetros de la clase son inmutables, y el compilador de Compose marca la clase como estable.
Anota con Stable
o Immutable
Una posible ruta para resolver problemas de estabilidad es anotar las clases inestables con @Stable
o @Immutable
.
Anotarlo anula lo que el compilador inferiría sobre tu clase. Es similar al operador !!
en Kotlin. Debes tener mucho cuidado con el uso de estas anotaciones. Anular el comportamiento del compilador podría generar errores imprevistos, por ejemplo, el elemento componible no se vuelve a componer cuando esperas que lo haga.
Si es posible hacer que tu clase sea estable sin una anotación, debes esforzarte por lograr la estabilidad de esa manera.
En el siguiente fragmento, se proporciona un ejemplo mínimo de una clase de datos anotada como inmutable:
@Immutable
data class Snack(
…
)
Ya sea que uses la anotación @Immutable
o @Stable
, el compilador de Compose marca la clase Snack
como estable.
Clases con anotaciones en colecciones
Considera un elemento componible que incluye un parámetro de tipo List<Snack>
:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Incluso si anotas Snack
con @Immutable
, el compilador de Compose seguirá marcando el parámetro snacks
en HighlightedSnacks
como inestable.
Los parámetros enfrentan el mismo problema que las clases en lo que respecta a los tipos de colecciones. El compilador de Compose siempre marca un parámetro de tipo List
como inestable, incluso cuando es una colección de tipos estables.
No puedes marcar un parámetro individual como estable ni anotar un elemento componible para que siempre se pueda omitir. Hay varios caminos a seguir.
Existen varias formas de solucionar el problema de las recopilaciones inestables. Estos diferentes enfoques se describen en las siguientes subsecciones.
Archivo de configuración
Si deseas cumplir con el contrato de estabilidad de tu base de código, puedes optar por considerar las colecciones de Kotlin como estables agregando kotlin.collections.*
a tu archivo de configuración de estabilidad.
Colección inmutable
Para garantizar la inmutabilidad del tiempo de compilación, puedes usar una colección inmutable de Kotlinx, en lugar de List
.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Si no puedes usar una colección inmutable, puedes crear una propia. Para ello, une List
en una clase estable con anotaciones. Según tus requisitos, es probable que un wrapper genérico sea la mejor opción para esto.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Luego, puedes usar esto como el tipo del parámetro en tu elemento componible.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Solución
Después de seguir cualquiera de estos enfoques, el compilador de Compose ahora marca el elemento componible HighlightedSnacks
como skippable
y restartable
.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
Durante la recomposición, Compose ahora puede omitir HighlightedSnacks
si ninguna de sus entradas cambió.
Archivo de configuración de estabilidad
A partir del compilador de Compose 1.5.5, se puede proporcionar un archivo de configuración de clases que se consideren estables en el tiempo de compilación. Esto permite considerar las clases que no controlas, por ejemplo, las clases de biblioteca estándar como LocalDateTime
, como estables.
El archivo de configuración es un archivo de texto sin formato con una clase por fila. Se admiten comentarios y comodines simples y dobles. Este es un ejemplo de configuración:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
Para habilitar esta función, pasa la ruta de acceso del archivo de configuración al bloque de opciones composeCompiler
de la configuración del complemento de Gradle del compilador de Compose.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Como el compilador de Compose se ejecuta en cada módulo de tu proyecto por separado, puedes proporcionar configuraciones diferentes a diferentes módulos si es necesario. Como alternativa, establece una configuración en el nivel raíz de tu proyecto y pasa esa ruta de acceso a cada módulo.
Varios módulos
Otro problema común involucra la arquitectura de varios módulos. El compilador de Compose solo puede inferir si una clase es estable si todos los tipos no primitivos a los que hace referencia están marcados explícitamente como estables o en un módulo que también se compiló con el compilador de Compose.
Si tu capa de datos está en un módulo independiente de la capa de IU, que es el enfoque recomendado, es posible que te encuentres con un problema.
Solución
Para resolver este problema, puedes seguir uno de los siguientes enfoques:
- Agrega las clases a tu archivo de configuración del compilador.
- Habilita el compilador de Compose en tus módulos de capas de datos o etiqueta tus clases con
@Stable
o@Immutable
cuando corresponda.- Esto implica agregar una dependencia de Compose a tu capa de datos. Sin embargo, es solo la dependencia del entorno de ejecución de Compose y no de
Compose-UI
.
- Esto implica agregar una dependencia de Compose a tu capa de datos. Sin embargo, es solo la dependencia del entorno de ejecución de Compose y no de
- Dentro del módulo de tu IU, une las clases de capa de datos en clases de wrapper específicas de la IU.
El mismo problema también ocurre cuando se usan bibliotecas externas si no usan el compilador de Compose.
No todos los elementos componibles se deben omitir
Cuando trabajes para solucionar problemas de estabilidad, no intentes hacer que se pueda omitir cada elemento componible. Si intentas hacerlo, es posible que la optimización prematura se introduzca más problemas de los que corrige.
Hay muchas situaciones en las que omitir no tiene ningún beneficio real y puede generar un código difícil de mantener. Por ejemplo:
- Un elemento componible que no se vuelve a componer con frecuencia o en absoluto.
- Un elemento componible que por sí solo llama a elementos que se pueden omitir
- Un elemento componible con una gran cantidad de parámetros con implementaciones de igualdad costosas. En este caso, el costo de verificar si cambió algún parámetro podría superar el costo de una recomposición económica.
Cuando un elemento componible se puede omitir, agrega una pequeña sobrecarga que puede no valer la pena. Incluso puedes anotar tu elemento componible para que sea no reiniciable en los casos en que determines que reiniciarlo genera más sobrecarga de la que vale la pena.