SideEffect — o que é, sincronização de estado no Jetpack Compose

Autor: IT Sectr Publicado: 2026-06-30 Tempo de leitura: 9 min

SideEffect é uma função composable no Jetpack Compose que executa o bloco de código passado em cada recomposição bem-sucedida. Ao contrário de LaunchedEffect e DisposableEffect, SideEffect não está vinculado a chaves e não possui bloco de limpeza — ele simplesmente sincroniza o estado do Compose com sistemas externos após cada renderização. Isso o torna ideal para atualizar funções callback, sincronizar com ViewPager e enviar dados para Analytics SDK. De acordo com Android Developers Documentation (2025), SideEffect é executado estritamente após o Compose confirmar uma recomposição bem-sucedida e não é executado se a recomposição for ignorada.

Pontos Principais

  • SideEffect — API de efeito colateral para código executado após cada recomposição bem-sucedida.
  • Sincronização — transfere o estado do Compose para sistemas externos que não suportam Compose.
  • Sem chaves — ao contrário de LaunchedEffect, SideEffect não reinicia, mas executa em cada recomposição.
  • Sem limpeza — SideEffect não fornece onDispose, é projetado apenas para sincronização unidirecional.
  • Síncrono — o bloco executa sincronamente dentro da fase de composição do Compose, sem corrotinas.

O que é SideEffect no Jetpack Compose

SideEffect é a mais simples das APIs de efeito colateral no Jetpack Compose. Ele executa um bloco de código em cada recomposição bem-sucedida de um componente composable. A palavra “bem-sucedida” é fundamental aqui: se o Compose decidir que a recomposição não é necessária (por exemplo, todos os parâmetros de entrada não mudaram e o resultado será o mesmo), o SideEffect não é executado. Isso garante que o bloco de sincronização seja chamado apenas quando a interface do usuário realmente mudou.

O principal caso de uso do SideEffect é sincronizar o estado do Compose com objetos que não fazem parte da árvore do Compose. Exemplos típicos incluem: atualizar uma função callback em um sistema Legacy View, passar o estado atual para ViewPager, enviar um evento para um SDK de análise quando os dados exibidos mudam e sincronizar com SDKs de mapas que esperam atualizações em formato externo.

De acordo com o Android Developer Blog (2025), SideEffect é frequentemente usado em conjunto com remember: remember preserva um objeto (por exemplo, um callback) e SideEffect o atualiza sempre que uma dependência muda. Este padrão é especialmente importante para bibliotecas que aceitam objetos listener e não os recriam na atualização — sem SideEffect, o listener manteria uma referência desatualizada ao estado atual.

kotlin
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setZoom(zoomLevel)
        mapView.updateMarkers(markers)
    }
    
    AndroidView(factory = { mapView })
}

Como o SideEffect funciona e as fases de composição

Para entender o SideEffect, é preciso compreender as fases de execução do Jetpack Compose. O Compose passa por três fases para cada quadro: Composition (o que exibir), Layout (onde exibir), Drawing (como exibir). O SideEffect é executado ao final da fase de Composition — depois que todas as funções composable foram executadas, mas antes da fase de Layout. Isso garante que o SideEffect veja o estado final de todas as variáveis após a recomposição.

Essa posição no ciclo de vida oferece uma vantagem importante: o SideEffect não pode causar recomposição infinita, mesmo que o estado seja alterado dentro dele. Por ser executado após a composição, as alterações feitas dentro do SideEffect serão aplicadas apenas no próximo quadro — isso evita os ciclos típicos de alterações de estado dentro do corpo de uma função composable (quando setState dentro da composição aciona uma nova composição antes que a atual termine).

Outra característica é que o SideEffect não é otimizado por chaves. Ele é executado em cada recomposição, independentemente de qual estado específico mudou. Se você precisar de um controle mais preciso (executar apenas quando um parâmetro específico mudar), use LaunchedEffect com chaves ou envolva o SideEffect em uma verificação de alteração via remember.

kotlin
// SideEffect optimized with remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }

SideEffect {
    if (currentZoom != zoomLevel) {
        map.animateToZoom(zoomLevel)
        currentZoom = zoomLevel
    }
}

// Without this check SideEffect would call animateToZoom
// on every recomposition, even if zoomLevel didn't change

Atualizando funções callback com SideEffect

O cenário prático mais comum para o SideEffect é atualizar funções callback que capturam o estado atual. No Jetpack Compose, isso é chamado de “gerenciamento do ciclo de vida de callbacks”. O problema é que as expressões lambda em Kotlin capturam variáveis por referência, e se um callback foi criado com um valor e depois a variável mudou — o callback continua usando o valor antigo.

Considere um exemplo: o SDK do Google Maps para Android aceita um objeto OnCameraMoveListener via setOnCameraMoveListener(). Se você passar uma lambda que captura isTrackingEnabled, quando isTrackingEnabled mudar a lambda não será atualizada — o SDK do Maps continuará chamando o callback antigo com dados desatualizados. O SideEffect resolve este problema: ele redefne o listener em cada recomposição, garantindo que o SDK sempre use a lambda atual com o estado mais recente.

De acordo com a Documentação do Maps SDK para Android (2025), o Google recomenda exatamente este padrão ao integrar o Maps com o Jetpack Compose. Uma abordagem semelhante é usada para WebView, VideoView, TextureView e qualquer outro componente baseado em View que aceite callbacks via métodos set. O SideEffect garante que os callbacks estejam atualizados a cada mudança de estado.

kotlin
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setOnMarkerClickListener { marker ->
            onMarkerClick(marker)
            true
        }
        mapView.isTrafficEnabled = isTrackingEnabled
    }
    
    AndroidView(factory = { mapView })
}

Sincronização com Analytics SDK

Outro cenário importante para o SideEffect é o envio de eventos para sistemas de análise quando o estado da interface do usuário muda. Por exemplo, quando um usuário alterna guias em um TabLayout dentro de uma tela Compose, o SideEffect pode passar o estado atual da guia selecionada para o Firebase Analytics ou AppsFlyer. Cada vez que a guia selecionada muda (e ocorre a recomposição), o SideEffect envia o evento correspondente.

A diferença do envio de eventos diretamente em onClick ou onTabSelected é que o SideEffect é acionado por mudanças de estado de qualquer fonte — não apenas ações do usuário, mas também alterações programáticas, restauração de estado após rotação de tela ou Deep Links. Isso torna o SideEffect um mecanismo de sincronização universal independente da fonte da mudança.

De acordo com as Firebase Best Practices (Google, 2025), o envio de eventos de análise via SideEffect fornece uma imagem mais completa da jornada do usuário, pois captura todas as mudanças de estado, incluindo aquelas que ocorrem sem ação direta do usuário. No entanto, é importante não exagerar: cada evento de análise é uma requisição de rede, portanto, para estados que mudam com frequência (posição de rolagem, coordenadas dos dedos), o SideEffect não é adequado — use debounce ou envie eventos apenas em mudanças significativas.

kotlin
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
    val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
    
    SideEffect {
        val params = Bundle().apply {
            putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
            putString(FirebaseAnalytics.Param.ITEM_ID, productId)
        }
        firebaseAnalytics.logEvent(        FirebaseAnalytics.Event.VIEW_ITEM, params)
    }
    
    // UI with TabRow and selected tab
}

SideEffect vs LaunchedEffect: quando usar cada um

A escolha entre SideEffect e LaunchedEffect depende de dois fatores: se a execução assíncrona é necessária e se o controle baseado em chaves é necessário. SideEffect é síncrono e executado em cada recomposição. LaunchedEffect é assíncrono (corrotina) e executado apenas quando uma chave muda, não em cada recomposição.

CaracterísticaSideEffectLaunchedEffect
ExecuçãoEm cada recomposiçãoNa mudança da chave
AssincroniaSíncronoCorrotina
ChavesNãoSim (vararg)
LimpezaNãoCancelamento automático da corrotina
Uso típicoCallbacks, Analytics, sincronização de visualizaçãoCarregamento, assinatura Flow, temporizadores

Na prática, 70% dos casos de uso de efeitos colaterais são cobertos pelo LaunchedEffect (operações assíncronas, carregamento de dados), 20% pelo DisposableEffect (recursos com limpeza) e apenas 10% pelo SideEffect (sincronização de callbacks). SideEffect é uma ferramenta especializada para um conjunto limitado de tarefas, mas nessas tarefas é indispensável.

Erros comuns com SideEffect

O principal erro é alterar o estado do Compose dentro do SideEffect. Embora o SideEffect não cause um loop infinito diretamente (por ser executado após a fase de composição), ele pode desencadear recomposições excessivas. Se o estado for alterado dentro do SideEffect (mutableStateOf), ele aciona uma nova recomposição no próximo quadro, que novamente executa o SideEffect — e assim por diante até a estabilização. Isso não é um loop infinito, mas é trabalho desnecessário para o framework.

O segundo erro é realizar cálculos pesados dentro do SideEffect. Como o SideEffect é chamado em cada recomposição, e as recomposições podem ocorrer dezenas de vezes por segundo (durante animações, rolagem), qualquer código pesado dentro do SideEffect levará a quedas de quadros. Movva operações pesadas para fora da composição — para uma corrotina (LaunchedEffect) ou calcule via derivedStateOf / remember.

O terceiro erro é tentar usar o SideEffect para código assíncrono. SideEffect não é uma função suspend, portanto delay(), await(), collect() e outras operações de corrotina dentro dele não compilarão. Se você precisar realizar uma ação assíncrona após a recomposição, use snapshotFlow { ... } em combinação com LaunchedEffect, ou inicie uma corrotina via rememberCoroutineScope.

Perguntas Frequentes

O SideEffect é executado na primeira composição?

Sim, o SideEffect é executado em cada composição bem-sucedida, incluindo a primeira — quando o componente aparece pela primeira vez na tela. Isso o diferencia do LaunchedEffect(Unit), que também é executado uma vez na primeira composição, mas não é executado em recomposições subsequentes (se a chave não mudou).

O SideEffect pode causar um loop infinito?

Não, o SideEffect é executado após a fase de composição — as alterações feitas dentro dele serão aplicadas apenas no próximo quadro, o que evita loops. No entanto, alterar o estado com frequência dentro do SideEffect pode causar uma cascata de recomposições, reduzindo o desempenho. Altere o estado dentro do SideEffect apenas quando absolutamente necessário.

Qual a diferença entre SideEffect e snapshotFlow?

O SideEffect é executado sincronamente em cada recomposição. O snapshotFlow cria um Flow a partir do estado do Compose e pode ser usado com collectLatest no LaunchedEffect para tratamento reativo de mudanças. O snapshotFlow é adequado para casos onde você precisa reagir a mudanças com debounce, filter ou distinctUntilChanged — o que é impossível no SideEffect síncrono.

Como depurar o SideEffect se ele for executado com muita frequência?

Use o Android Studio Compose Modifier Debugger ou adicione registro com o nome do componente e a frequência de chamadas. Se o SideEffect for executado com mais frequência do que o esperado, verifique se o estado do componente pai está mudando desnecessariamente. Otimização: extraia partes estáveis da interface do usuário em funções composable separadas com anotações unstable para reduzir o número de recomposições.

É possível combinar SideEffect com DisposableEffect?

Sim, eles podem ser usados no mesmo componente para propósitos diferentes. O DisposableEffect é responsável por configurar e limpar um recurso (uma vez), enquanto o SideEffect lida com a sincronização do estado atual com esse recurso em cada recomposição. Um exemplo típico: DisposableEffect registra um callback via API, e SideEffect atualiza os dados capturados nesse callback a cada mudança.

Resumo

  • SideEffect — API de efeito colateral para código síncrono executado após cada recomposição bem-sucedida no Jetpack Compose.
  • Sincronização — o principal caso de uso: transferir o estado do Compose para sistemas externos (Google Maps, WebView, ViewPager, Analytics SDK).
  • Callbacks — SideEffect garante que as funções callback que capturam o estado atual permaneçam atualizadas a cada mudança da interface do usuário.
  • Sem loops — executado após a fase de composição, portanto alterar o estado dentro do SideEffect não causa recomposição infinita.
  • Limitações — não suporta chaves, assincronia ou bloco de limpeza; para essas tarefas use LaunchedEffect ou DisposableEffect.
  • Desempenho — evite cálculos pesados dentro do SideEffect, pois ele é executado em cada recomposição (até 60 vezes por segundo).
  • Depuração — controle a frequência de chamadas através do Compose Debugger e otimize com remember para filtrar recomposições desnecessárias.

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.

Discutir o projeto

Leia também