Control de efectos secundarios en Compose: Cuándo usar LaunchedEffect y SideEffect

  • Diferenciación entre el renderizado de la interfaz y la ejecución de lógica externa mediante el uso de Effects.
  • Implementación de corrutinas seguras con LaunchedEffect y rememberCoroutineScope según el ciclo de vida.
  • Optimización del rendimiento de la UI mediante el uso de derivedStateOf y la gestión de estados con produceState.
  • Gestión de la limpieza de recursos y sincronización de estados externos mediante DisposableEffect y SideEffect.

Control de efectos secundarios en Compose Cuándo usar LaunchedEffect y SideEffect

Cuando nos metemos de lleno en el desarrollo con Jetpack Compose, es normal que al principio nos choque un poco la idea de que las funciones de componibilidad se pueden ejecutar muchas veces y en cualquier orden. Básicamente, entrar en un composable es como caminar sobre lava: no puedes ejecutar cualquier código ahí dentro porque, si lo haces, podrías acabar lanzando diez Toasts seguidos o disparando eventos de analítica cada vez que la pantalla parpadee.

Para evitar este caos, Compose nos ofrece los llamados efectos secundarios. Estos son entornos controlados y seguros donde podemos colocar la lógica que no tiene que ver con dibujar la interfaz, asegurándonos de que el código se ejecute exactamente cuando queremos y no cuando el framework decida recomponer el elemento.

Dominando LaunchedEffect para tareas asíncronas

Si necesitas lanzar una corrutina que viva lo mismo que tu elemento en pantalla, LaunchedEffect es tu mejor aliado. Esta función inicia un bloque de código suspendido en cuanto el composable entra en la composición y, lo más importante, se encarga de cancelar automáticamente esa tarea si el elemento desaparece de la vista.

Un detalle clave son las claves que le pasamos. Si el valor de una clave cambia, el efecto se cancela y se reinicia desde cero. Esto es genial para animaciones que dependen de una variable o para cargar datos de una API cuando el ID del usuario cambia. Ahora bien, si quieres que algo se ejecute una sola vez en toda la vida del componente, puedes pasarle Unit o true como parámetro.

A veces ocurre que necesitamos ejecutar una función de suspensión, pero no estamos dentro de la composición, sino en un evento de usuario, como el clic de un botón. En esos casos, no podemos usar LaunchedEffect. La solución es rememberCoroutineScope, que nos da un alcance vinculado al ciclo de vida del composable pero que podemos disparar manualmente mediante un scope.launch.

Artículo relacionado:
Guía Completa del Ciclo de Vida de una Actividad en Android: Desde Conceptos Básicos hasta Jetpack Compose

Evitando reinicios innecesarios con rememberUpdatedState

Hay situaciones peliagudas donde tenemos un efecto de larga duración y no queremos que se reinicie aunque cambien algunas variables. Aquí es donde entra en juego rememberUpdatedState. Esta herramienta nos permite capturar la versión más actual de una lambda o un valor sin que esto provoque que el LaunchedEffect vuelva a empezar la cuenta atrás o el proceso.

Es especialmente útil en pantallas de carga o splash screens. Imagina que tienes un temporizador que debe avisar cuando el tiempo ha terminado; si la función de callback cambia mientras el reloj corre, ya no necesitas reiniciar el delay, simplemente quieres que, al finalizar, se ejecute la última versión de esa función.

Sincronización y limpieza: SideEffect y DisposableEffect

No todo son corrutinas. A veces solo necesitamos que Compose le diga a una librería externa que algo ha cambiado. SideEffect se ejecuta tras cada recomposición exitosa. Es la herramienta perfecta para sincronizar el estado de la UI con objetos que no son gestionados por Compose, como actualizar una propiedad de usuario en un servicio de analíticas de Firebase.

Por otro lado, hay efectos que dejan una huella y necesitan ser borrados para no dejar fugas de memoria. Para esto usamos DisposableEffect. A diferencia de sus hermanos, este nos obliga a incluir un bloque onDispose al final. Es el lugar ideal para desregistrar observadores de ciclo de vida o evitar memory leaks al cerrar suscripciones a sensores del dispositivo.

Ciclo de Vida de las Activity
Artículo relacionado:
Guía Completa sobre el Ciclo de Vida de las Activity en Android
  • Sincronización simple: SideEffect para actualizar estados externos.
  • Gestión de recursos: DisposableEffect para evitar memory leaks.
  • Flujos de datos: snapshotFlow para convertir estados de Compose en flujos de Kotlin.

Optimización del estado con derivedStateOf y produceState

Control de efectos secundarios en Compose: Cuándo usar LaunchedEffect y SideEffect

Cuando tenemos un estado que cambia constantemente, como la posición de scroll de una lista, podemos acabar con recomposiciones excesivas que matan el rendimiento. Para solucionar esto, derivedStateOf es la solución. Crea un estado derivado que solo activa la recomposición cuando el resultado final cambia, actuando de forma similar a un filtro de «distinto hasta que cambie».

Es un error común usarlo para concatenar dos strings sencillos; en ese caso es un gasto de recursos innecesario. Debes reservarlo para cálculos basados en umbrales, como mostrar un botón de «volver arriba» solo cuando se gestionan listas eficientes con LazyColumn y el índice del primer elemento visible sea mayor a cero.

Para aquellos que vienen de usar LiveData o Flow y quieren convertir esos datos en algo que Compose entienda, produceState es la vía. Lanza una corrutina que puede emitir valores a un objeto State. Básicamente, envuelve la lógica de obtención de datos y la expone como un estado reactivo que la UI puede observar cómodamente.

Si trabajamos con flujos de datos en la capa de ViewModel, la recomendación actual es usar collectAsStateWithLifecycle. Esta función es superior a la recolección básica porque es consciente del ciclo de vida de Android, deteniendo la recolección cuando la app está en segundo plano y ahorrando batería y memoria de forma inteligente.

Tener un control total sobre los efectos secundarios implica saber elegir la herramienta justa para cada tarea, ya sea mediante la encapsulación de estados en clases contenedoras para facilitar las pruebas o el uso de Saves personalizados con rememberSaveable para que la información no se pierda al rotar el dispositivo. Al final, se trata de mantener el flujo de datos unidireccional y asegurar que la lógica de negocio no ensucie el proceso de renderizado de la interfaz.

Kotlin Coroutines: Dominando launch, async y await
Artículo relacionado:
Guía Completa de Kotlin para Desarrolladores Android: Conceptos y Fundamentos

Surfshark Antivirus para Android
Puede que le interese:
Cómo eliminar virus en Android: Guía Completa y Actualizada para Limpiar tu Móvil
Add as preferred source