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 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.
@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
}
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.
// 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)
}
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ística | LaunchedEffect | DisposableEffect |
|---|---|---|
| Ejecución | Asíncrona (corrutina) | Sincrónica |
| onDispose | No (cancelación automática) | Sí (bloque de limpieza explícito) |
| Claves | Reinicio + cancelar corrutina anterior | Ejecutar onDispose + reinicializar |
| Uso típico | Solicitudes de red, suscripciones Flow, temporizadores | BroadcastReceiver, sensores, listeners nativos |
| Cancelación al salir | Automática | Mediante 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.
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.
@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) }
}
}
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.
// 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)
}
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
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.
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.
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.
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.
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
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.
Lea también