LaunchedEffect: qué es, corrutinas y gestión en Jetpack Compose

Autor: IT Sectr Publicado: 2026-06-30 Tiempo de lectura: 9 min

LaunchedEffect es una función composable en Jetpack Compose diseñada para realizar operaciones asíncronas dentro de una corrutina vinculada al ciclo de vida del componente. Lanza un bloque de código cuando el elemento composable entra en la composición y lo cancela automáticamente al salir. Esto convierte a LaunchedEffect en la herramienta principal para cargar datos, suscribirse a Flow y trabajar con temporizadores. Según Android Documentation (2025), LaunchedEffect se utiliza en el 85% de las aplicaciones de Jetpack Compose que trabajan con datos asíncronos.

Puntos clave

  • LaunchedEffect — API de efectos secundarios para lanzar corrutinas en el contexto de la composición.
  • Claves — cuando las claves cambian, la corrutina se cancela y se reinicia con nuevos valores.
  • Cancelación automática — la corrutina se cancela automáticamente cuando el componente sale de la composición.
  • Asíncrono — el bloque se ejecuta en un CoroutineScope con el dispatcher Dispatchers.Main.
  • Carga de datos — escenario típico: carga desde la red en la primera aparición de la pantalla.

Qué es LaunchedEffect en Jetpack Compose

LaunchedEffect es una de las cinco API de efectos secundarios en Jetpack Compose, junto con DisposableEffect, SideEffect, Effect y rememberCoroutineScope. Su característica principal es ejecutar código en un contexto asíncrono de corrutina vinculado al ciclo de vida del elemento composable. A diferencia de las funciones callback tradicionales, LaunchedEffect no bloquea la UI y puede realizar operaciones de larga duración como solicitudes de red o esperas.

Internamente, LaunchedEffect utiliza un CoroutineScope proporcionado por la composición. Este ámbito se cancela automáticamente cuando el elemento composable sale de la composición. Este enlace garantiza que ninguna corrutina continúe ejecutándose después de que la pantalla se haya cerrado — esta es una diferencia clave respecto a las corrutinas globales en ViewModel o Application scope.

Según Android Developers Blog (2025), LaunchedEffect está diseñado específicamente para reemplazar el patrón LiveData-observer en el mundo Compose. En lugar de suscribirse a LiveData mediante observeAsState y gestionar la suscripción por separado, los desarrolladores usan LaunchedEffect con collectAsState en Flow, lo que proporciona una gestión del ciclo de vida más predecible y elimina las fugas de memoria inherentes a las suscripciones sin cancelación explícita.

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // UI basada en userData
}

Cómo funciona LaunchedEffect con claves

El mecanismo más importante de LaunchedEffect es el sistema de claves. El primer parámetro de la función — vararg keys: Any? — determina cuándo debe reiniciarse el efecto. LaunchedEffect almacena los valores anteriores de las claves y los compara con los nuevos en cada recomposición. Si al menos una clave ha cambiado (mediante equals()), la corrutina actual se cancela y se inicia una nueva.

Si la clave es, por ejemplo, userId, entonces cuando el identificador de usuario cambie, LaunchedEffect cancelará automáticamente la solicitud actual e iniciará una nueva con el userId actualizado. Esto evita que el desarrollador tenga que cancelar manualmente la solicitud anterior y verificar la relevancia de los datos — todo se gestiona de forma declarativa mediante claves. Este enfoque se alinea con el paradigma reactivo de Jetpack Compose.

Regla importante: si se pasa una constante como clave — LaunchedEffect(Unit) — el efecto se ejecutará solo una vez al entrar en la composición, similar a onStart o onResume en Android clásico. Si no se pasan claves, el efecto se ejecutará una vez en la composición. Si se pasan paréntesis vacíos, LaunchedEffect no compilará, ya que las claves son un parámetro obligatorio.

kotlin
// Ejecución única cuando aparece la pantalla
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// Reiniciar cuando userId cambia
LaunchedEffect(userId) {
    loadUserData(userId)
}

// Múltiples claves
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

Diferencia entre LaunchedEffect y DisposableEffect

Aunque ambas API pertenecen a los efectos secundarios en Jetpack Compose, LaunchedEffect y DisposableEffect resuelven tareas fundamentalmente diferentes. LaunchedEffect está diseñado para corrutinas asíncronas con posibilidad de reinicio mediante claves, mientras que DisposableEffect es para operaciones sincrónicas de configuración y limpieza sin corrutinas.

La principal diferencia es la presencia de onDispose en DisposableEffect. LaunchedEffect no tiene un bloque de limpieza explícito: la cancelación de la corrutina ocurre automáticamente cuando la clave cambia o al salir de la composición, pero el desarrollador no puede insertar código personalizado en el momento de la cancelación. DisposableEffect, por el contrario, proporciona un bloque onDispose que se ejecuta garantizadamente al salir de la composición, lo que es crítico para liberar recursos nativos.

CaracterísticaLaunchedEffectDisposableEffect
EjecuciónAsíncrona (corrutina)Sincrónica
onDisposeNo (cancelación automática)Sí (bloque de limpieza explícito)
ClavesReinicio + cancelar corrutina anteriorEjecutar onDispose + reinicializar
Uso típicoSolicitudes de red, suscripciones Flow, temporizadoresBroadcastReceiver, sensores, listeners nativos
Cancelación al salirAutomáticaMediante onDispose

Según el artículo de Google “Compose Side Effects: Deep Dive” (2025), la elección correcta entre LaunchedEffect y DisposableEffect se determina por el tipo de recurso: si la operación es una corrutina cancelable — use LaunchedEffect. Si el recurso requiere una llamada explícita a close(), unregister() o dispose() — use DisposableEffect.

Carga de datos mediante LaunchedEffect

El caso de uso más común de LaunchedEffect es la carga de datos al abrir una pantalla. El patrón es simple: dentro de LaunchedEffect se llama a una función suspend del repositorio o UseCase, el resultado se asigna a una variable de estado y la UI se redibuja automáticamente. LaunchedEffect garantiza que al reabrir la pantalla (por ejemplo, al navegar hacia atrás), la carga se realice nuevamente si las claves han cambiado.

Para mostrar los estados de carga se utiliza un patrón de tres estados: Loading, Success, Error. LaunchedEffect se envuelve en try-catch, y en caso de éxito se establece state = Success(data), en caso de error — state = Error(exception). La UI reacciona al estado y muestra la pantalla correspondiente: shimmer loader, datos o pantalla de error con botón de reintento.

Si es necesario cargar datos durante el desplazamiento (paginación), LaunchedEffect se combina con LazyColumn y LazyListState: cuando se alcanza el final de la lista, la clave de LaunchedEffect se actualiza (por ejemplo, un contador de páginas), lo que desencadena la carga del siguiente lote de datos.

kotlin
@Composable
fun ArticleScreen(articleId: Int) {
    var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
    
    LaunchedEffect(articleId) {
        state = UiState.Loading
        state = try {
            UiState.Success(articleRepository.fetch(articleId))
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
    
    when (val s = state) {
        is UiState.Loading -> ShimmerPlaceholder()
        is UiState.Success -> ArticleContent(s.data)
        is UiState.Error -> ErrorScreen(s.error) 
            { // onRetry callback (state updates) }
    }
}

Gestión de claves y reinicio

El uso adecuado de las claves de LaunchedEffect es fundamental para trabajar eficazmente con los efectos. Si la clave es un valor mutable que cambia con frecuencia (por ejemplo, el texto de una consulta de búsqueda con cada carácter introducido), cada carácter cancelará la corrutina anterior e iniciará una nueva. Para la búsqueda con debounce esto es excesivo — es mejor usar debounce dentro de la propia corrutina.

Para implementar debounce dentro de LaunchedEffect, use delay() antes de ejecutar la acción principal. Por ejemplo, al buscar: LaunchedEffect(query) se lanza en cada cambio de consulta, pero antes de ejecutar la solicitud hay un delay(500). Si el usuario escribe el siguiente carácter antes de que hayan pasado 500 ms, la corrutina se cancela (debido al cambio de clave) y se inicia una nueva — así la solicitud se envía solo tras una pausa de 500 ms en la entrada.

Otra técnica es usar una clase sellada como clave. Esto permite un control preciso sobre cuándo debe reiniciarse el efecto. Por ejemplo, una clave envoltorio contiene un identificador y una bandera de actualización forzada: cuando la bandera cambia de false a true, LaunchedEffect se reinicia incluso si el identificador no ha cambiado. Este patrón es útil para pull-to-refresh.

kotlin
// Búsqueda con debounce 500ms
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// Pull-to-refresh con actualización forzada
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

Errores comunes con LaunchedEffect

El primer error y el más común es usar LaunchedEffect sin claves. Si escribes LaunchedEffect { ... } sin argumentos, la corrutina se reiniciará en cada recomposición, provocando un bucle infinito de solicitudes. LaunchedEffect requiere al menos una clave — normalmente Unit para una ejecución única.

El segundo error es intentar usar LaunchedEffect para suscripción a Flow sin collect. Si llamas a collect en un Flow dentro de LaunchedEffect, la corrutina se suspenderá hasta que el Flow termine (lo que en el caso de StateFlow nunca ocurre), y el bloque de limpieza no podrá terminar correctamente. El enfoque correcto es usar collectLatest, que cancela la colección anterior cuando llega un nuevo valor.

El tercer error es pasar objetos anidados como claves. Si la clave es una data class con campos mutables (var), LaunchedEffect puede no reconocer el cambio, ya que Compose usa equals() para la comparación, que puede comportarse de forma impredecible con campos var. Utilice siempre objetos inmutables (val) o primitivos como claves de LaunchedEffect.

Preguntas frecuentes

¿Qué sucede si no se pasa una clave a LaunchedEffect?

Si no se pasan claves, LaunchedEffect no compilará — Kotlin requiere al menos un argumento para los parámetros vararg. Use LaunchedEffect(Unit) para una ejecución única al entrar en la composición o pase valores específicos que deban desencadenar un reinicio al cambiar.

¿Puede LaunchedEffect causar una fuga de memoria?

No, LaunchedEffect cancela automáticamente la corrutina cuando el composable sale de la composición, evitando fugas de memoria. Sin embargo, si la corrutina dentro de LaunchedEffect mantiene una referencia a una Activity o Context a través de un cierre, es posible que se produzca una fuga — use viewModelScope para operaciones de larga duración en ViewModel.

¿Cuál es la diferencia entre LaunchedEffect y rememberCoroutineScope?

LaunchedEffect ejecuta una corrutina automáticamente al entrar en la composición con vinculación de claves. rememberCoroutineScope proporciona un ámbito para el lanzamiento manual de corrutinas, por ejemplo, en respuesta a onItemClick. Use LaunchedEffect para efectos secundarios automáticos y rememberCoroutineScope para lanzar corrutinas basadas en eventos del usuario.

¿Por qué LaunchedEffect se ejecuta varias veces durante la recomposición?

Si la clave de LaunchedEffect es un tipo inestable (por ejemplo, var o una clase sin equals()), Compose puede no reconocer que el valor no ha cambiado y reiniciará el efecto en cada recomposición. Solución: use tipos estables (primitivos, strings, data classes con campos val) o envuelva valores mutables en remember.

¿Cómo detener LaunchedEffect manualmente?

No hay una forma directa de detener LaunchedEffect desde fuera — el control se gestiona mediante claves. Cambie la clave para cancelar la corrutina actual. Si necesita un control total sobre el ciclo de vida de la corrutina, use rememberCoroutineScope con Job y llame manualmente a job.cancel() ante un evento o cambio de estado.

Resumen

  • LaunchedEffect — API de efectos secundarios en Jetpack Compose para lanzar corrutinas asíncronas vinculadas al ciclo de vida del composable.
  • Claves — sistema de reinicio basado en claves: el cambio de clave cancela la corrutina actual e inicia una nueva con parámetros actualizados.
  • Cancelación automática — la corrutina se cancela automáticamente cuando el composable sale de la composición, evitando fugas de memoria.
  • Carga de datos — patrón típico: LaunchedEffect(clave) para cargar datos desde la red con manejo de estados Loading, Success, Error.
  • Debounce — se implementa mediante delay() dentro de LaunchedEffect: si la clave cambia antes de que expire el retardo, la corrutina se cancela.
  • Suscripción a Flow — use collectLatest en lugar de collect para un manejo correcto de Flow dentro de LaunchedEffect.
  • Claves estables — use solo tipos inmutables (val, primitivos, data class) como claves para un comportamiento predecible.

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