Noticia Control de efectos secundarios en Compose: Cuándo usar LaunchedEffect 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.

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.

  • 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.

Continúar leyendo...