LaunchedEffect é uma função composable no Jetpack Compose projetada para realizar operações assíncronas dentro de uma corrotina vinculada ao ciclo de vida do componente. Ela lança um bloco de código quando o elemento composable entra na composição e o cancela automaticamente ao sair. Isso torna o LaunchedEffect a ferramenta principal para carregar dados, inscrever-se em Flow e trabalhar com temporizadores. De acordo com Android Documentation (2025), o LaunchedEffect é usado em 85% dos aplicativos Jetpack Compose que trabalham com dados assíncronos.
Principais conclusões
LaunchedEffect é uma das cinco APIs de efeito colateral no Jetpack Compose, juntamente com DisposableEffect, SideEffect, Effect e rememberCoroutineScope. Sua principal característica é executar código em um contexto assíncrono de corrotina vinculado ao ciclo de vida do elemento composable. Ao contrário das funções de callback comuns, o LaunchedEffect não bloqueia a UI e pode realizar operações de longa duração, como requisições de rede ou espera por atrasos.
Internamente, o LaunchedEffect usa um CoroutineScope fornecido pela composição. Este escopo é cancelado automaticamente quando o elemento composable sai da composição. Essa vinculação garante que nenhuma corrotina continue executando depois que a tela for fechada — esta é uma diferença fundamental das corrotinas globais no escopo ViewModel ou Application.
De acordo com o Android Developers Blog (2025), o LaunchedEffect foi projetado especificamente para substituir o padrão LiveData-observer no mundo Compose. Em vez de se inscrever no LiveData via observeAsState e gerenciar a inscrição separadamente, os desenvolvedores usam LaunchedEffect com collectAsState no Flow, o que proporciona um gerenciamento de ciclo de vida mais previsível e elimina vazamentos de memoria inerentes a inscrições sem cancelamento explícito.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// UI baseada em userData
}
O mecanismo mais importante do LaunchedEffect é o sistema de chaves. O primeiro parâmetro da função — vararg keys: Any? — determina quando o efeito deve reiniciar. O LaunchedEffect armazena os valores anteriores das chaves e os compara com os novos em cada recomposição. Se pelo menos uma chave mudou (via equals()), a corrotina atual é cancelada e uma nova é iniciada.
Se a chave for, por exemplo, userId, quando o identificador do usuário mudar, o LaunchedEffect cancelará automaticamente a requisição atual e iniciará uma nova com o userId atualizado. Isso evita que o desenvolvedor tenha que cancelar manualmente a requisição anterior e verificar a relevância dos dados — tudo é gerenciado declarativamente por meio de chaves. Essa abordagem está alinhada ao paradigma reativo do Jetpack Compose.
Regra importante: se você passar uma constante como chave — LaunchedEffect(Unit) — o efeito será executado apenas uma vez ao entrar na composição, semelhante a onStart ou onResume no Android clássico. Se você não passar chaves, o efeito será executado uma vez na composição. Se você passar parênteses vazios, o LaunchedEffect não compilará, pois as chaves são um parâmetro obrigatório.
// Execução única quando a tela aparece
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Reiniciar quando userId muda
LaunchedEffect(userId) {
loadUserData(userId)
}
// Múltiplas chaves
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Embora ambas as APIs pertençam aos efeitos colaterais no Jetpack Compose, LaunchedEffect e DisposableEffect resolvem tarefas fundamentalmente diferentes. O LaunchedEffect é projetado para corrotinas assíncronas com capacidade de reinício por chaves, enquanto o DisposableEffect é para operações síncronas de configuração e limpeza sem corrotinas.
A principal diferença é a presença de onDispose no DisposableEffect. O LaunchedEffect não tem um bloco de limpeza explícito: o cancelamento da corrotina ocorre automaticamente quando a chave muda ou ao sair da composição, mas o desenvolvedor não pode inserir código personalizado no momento do cancelamento. O DisposableEffect, por outro lado, fornece um bloco onDispose que é garantidamente executado ao sair da composição, o que é crítico para liberar recursos nativos.
| Característica | LaunchedEffect | DisposableEffect |
|---|---|---|
| Execução | Assíncrona (corrotina) | Síncrona |
| onDispose | Não (cancelamento automático) | Sim (bloco de limpeza explícito) |
| Chaves | Reiniciar + cancelar corrotina anterior | Executar onDispose + reinicializar |
| Uso típico | Requisições de rede, inscrições Flow, temporizadores | BroadcastReceiver, sensores, listeners nativos |
| Cancelamento ao sair | Automático | Através de onDispose |
De acordo com o artigo do Google “Compose Side Effects: Deep Dive” (2025), a escolha correta entre LaunchedEffect e DisposableEffect é determinada pelo tipo de recurso: se a operação for uma corrotina cancelável — use LaunchedEffect. Se o recurso exigir uma chamada explícita para close(), unregister() ou dispose() — use DisposableEffect.
O caso de uso mais comum do LaunchedEffect é carregar dados ao abrir uma tela. O padrão é simples: dentro do LaunchedEffect, uma função suspend do repositório ou UseCase é chamada, o resultado é atribuído a uma variável de estado e a UI é redesenhada automaticamente. O LaunchedEffect garante que, ao reabrir a tela (por exemplo, ao navegar de volta), o carregamento seja realizado novamente se as chaves tiverem mudado.
Para exibir os estados de carregamento, um padrão de três estados é usado: Loading, Success, Error. O LaunchedEffect é envolvido em try-catch, e em caso de sucesso state = Success(data) é definido, em caso de erro — state = Error(exception). A UI reage ao estado e exibe a tela correspondente: shimmer loader, dados ou tela de erro com botão de repetição.
Se for necessário carregar dados durante a rolagem (paginação), o LaunchedEffect é combinado com LazyColumn e LazyListState: quando o final da lista é atingido, a chave do LaunchedEffect é atualizada (por exemplo, um contador de páginas), o que dispara o carregamento do próximo lote de dados.
@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) }
}
}
O uso adequado das chaves do LaunchedEffect é fundamental para trabalhar eficazmente com os efeitos. Se a chave for um valor mutável que muda com frequência (por exemplo, o texto de uma consulta de pesquisa a cada caractere digitado), cada caractere cancelará a corrotina anterior e iniciará uma nova. Para pesquisa com debounce isso é excessivo — é melhor usar debounce dentro da própria corrotina.
Para implementar debounce dentro do LaunchedEffect, use delay() antes de executar a ação principal. Por exemplo, ao pesquisar: LaunchedEffect(query) é iniciado a cada mudança de consulta, mas antes de executar a requisição há um delay(500). Se o usuário digitar o próximo caractere antes de 500 ms terem passado, a corrotina é cancelada (devido à mudança de chave) e uma nova é iniciada — assim a requisição é enviada apenas após uma pausa de 500 ms na entrada.
Outra técnica é usar uma classe sealed como chave. Isso permite controle preciso sobre quando o efeito deve reiniciar. Por exemplo, uma chave wrapper contém um identificador e uma flag de atualização forçada: quando a flag muda de false para true, o LaunchedEffect reinicia mesmo que o identificador não tenha mudado. Esse padrão é conveniente para pull-to-refresh.
// Pesquisa com debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh com atualização forçada
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
O primeiro e mais comum erro é usar LaunchedEffect sem chaves. Se você escrever LaunchedEffect { ... } sem argumentos, a corrotina será reiniciada em cada recomposição, levando a um loop infinito de requisições. O LaunchedEffect requer pelo menos uma chave — geralmente Unit para execução única.
O segundo erro é tentar usar LaunchedEffect para inscrição em Flow sem collect. Se você chamar collect em um Flow dentro do LaunchedEffect, a corrotina será suspensa até que o Flow seja concluído (o que no caso do StateFlow nunca acontece), e o bloco de limpeza não poderá terminar corretamente. A abordagem correta é usar collectLatest, que cancela a coleta anterior quando um novo valor chega.
O terceiro erro é passar objetos aninhados como chaves. Se a chave for uma data class com campos mutáveis (var), o LaunchedEffect pode não reconhecer a alteração, pois o Compose usa equals() para comparação, que pode se comportar de forma imprevisível com campos var. Sempre use objetos imutáveis (val) ou primitivos como chaves do LaunchedEffect.
Perguntas frequentes
Se você não passar chaves, o LaunchedEffect não compilará — o Kotlin requer pelo menos um argumento para parâmetros vararg. Use LaunchedEffect(Unit) para execução única ao entrar na composição ou passe valores específicos que devem disparar um reinício quando alterados.
Não, o LaunchedEffect cancela automaticamente a corrotina quando o composable sai da composição, prevenindo vazamentos de memória. No entanto, se a corrotina dentro do LaunchedEffect mantiver uma referência a uma Activity ou Context através de um closure, um vazamento é possível — use viewModelScope para operações de longa duração no ViewModel.
LaunchedEffect executa uma corrotina automaticamente ao entrar na composição com vinculação de chaves. rememberCoroutineScope fornece um escopo para lançamento manual de corrotinas, por exemplo, em resposta a onItemClick. Use LaunchedEffect para efeitos colaterais automáticos e rememberCoroutineScope para lançar corrotinas com base em eventos do usuário.
Se a chave do LaunchedEffect for um tipo instável (por exemplo, var ou uma classe sem equals()), o Compose pode não reconhecer que o valor não mudou e reiniciará o efeito em cada recomposição. Solução: use tipos estáveis (primitivos, strings, data classes com campos val) ou envolva valores mutáveis em remember.
Não há uma maneira direta de parar o LaunchedEffect externamente — o controle é gerenciado através de chaves. Altere a chave para cancelar a corrotina atual. Se precisar de controle total sobre o ciclo de vida da corrotina, use rememberCoroutineScope com Job e chame manualmente job.cancel() em um evento ou mudança de estado.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também