SideEffect — co to je, synchronizace stavu v Jetpack Compose

Autor: IT Sectr Publikováno: 2026-06-30 Doba čtení: 9 min

SideEffect — je composable funkce v Jetpack Compose, která provádí předaný blok kódu při každé úspěšné rekompozici. Na rozdíl od LaunchedEffect a DisposableEffect není SideEffect vázán na klíče a nemá blok čištění — jednoduše synchronizuje stav Compose s externími systémy po každém renderování. Díky tomu je ideální pro aktualizaci callback funkcí, synchronizaci s ViewPager a předávání dat do Analytics SDK. Podle Android Developers Documentation (2025) se SideEffect provádí striktně poté, co Compose potvrdí úspěšnou rekompozici, a neprovádí se, pokud byla rekompozice přeskočena.

Hlavní body

  • SideEffect — side-effect API pro kód prováděný po každé úspěšné rekompozici.
  • Synchronizace — předává stav Compose externím systémům, které nepodporují Compose.
  • Bez klíčů — na rozdíl od LaunchedEffect se SideEffect nerestartuje, ale provádí se při každé rekompozici.
  • Žádné čištění — SideEffect neposkytuje onDispose, je určen pouze pro jednosměrnou synchronizaci.
  • Synchronicita — blok se provádí synchronně s fází kompozice Compose, bez korutin.

Co je SideEffect v Jetpack Compose

SideEffect — je nejjednodušší ze side-effect API v Jetpack Compose. Provádí blok kódu při každé úspěšné rekompozici composable komponenty. Slovo „úspěšné“ je zde klíčové: pokud Compose rozhodne, že rekompozice není nutná (například všechny vstupní parametry se nezměnily a výsledek bude stejný), SideEffect se neprovede. To zaručuje, že blok synchronizace je volán pouze tehdy, když se UI skutečně změnilo.

Hlavní scénář použití SideEffect — synchronizace stavu Compose s objekty, které nejsou součástí stromu Compose. Typické příklady: aktualizace callback funkce v Legacy View systému, předání aktuálního stavu do ViewPager, odeslání události do Analytics SDK při změně zobrazených dat, synchronizace s mapovými SDK, která očekávají aktualizace v externím formátu.

Podle Android Developer Blog (2025) se SideEffect často používá ve spojení s remember: remember ukládá objekt (například callback) a SideEffect jej aktualizuje při každé změně závislosti. Tento vzor je obzvláště důležitý pro knihovny, které přijímají listener objekty a při aktualizaci je znovu nevytvářejí — bez SideEffect by listener obsahoval zastaralý odkaz na aktuální stav.

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 })
}

Jak funguje SideEffect a fáze kompozice

Pro pochopení SideEffect je třeba znát fáze provádění Jetpack Compose. Compose prochází třemi fázemi pro každý snímek: Composition (co zobrazit), Layout (kde zobrazit), Drawing (jak zobrazit). SideEffect se provádí na konci fáze Composition — poté, co všechny composable funkce proběhly, ale před fází Layout. To zaručuje, že SideEffect vidí konečný stav všech proměnných po rekompozici.

Tato pozice v životním cyklu poskytuje důležitou výhodu: SideEffect nemůže způsobit nekonečnou rekompozici, i když se v něm stav změní. Protože se provádí po kompozici, změny provedené uvnitř SideEffect budou zohledněny až v dalším snímku — to zabraňuje smyčkám typickým pro změny uvnitř těla composable funkce (když setState uvnitř kompozice spustí novou kompozici před dokončením té stávající).

Další vlastnost — SideEffect není optimalizován klíči. Provádí se při každé rekompozici bez ohledu na to, který stav se změnil. Pokud je vyžadována přesnější kontrola (provádění pouze při změně konkrétního parametru), použijte LaunchedEffect s klíči nebo obalte SideEffect do kontroly změny přes remember.

kotlin
// SideEffect optimalizovaný pomocí remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }

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

// Bez této kontroly by SideEffect volal animateToZoom
// při každé rekompozici, i když se zoomLevel nezměnil

Aktualizace callback funkcí přes SideEffect

Nejčastější praktický scénář pro SideEffect — aktualizace callback funkcí uzavírajících aktuální stav. V Jetpack Compose se tomu říká „správa životního cyklu callbacků“. Problém je v tom, že lambda výrazy v Kotlin zachycují proměnné odkazem, a pokud byl callback vytvořen s jednou hodnotou proměnné a pak se proměnná změnila — callback nadále používá starou hodnotu.

Uvažujme příklad: Google Maps SDK pro Android přijímá objekt OnCameraMoveListener přes setOnCameraMoveListener(). Pokud předáte lambdu, která zachycuje isTrackingEnabled, pak při změně isTrackingEnabled se lambda neaktualizuje — Maps SDK bude nadále volat starý callback se zastaralými daty. SideEffect řeší tento problém: znovu nastaví listener při každé rekompozici, čímž zaručuje, že SDK vždy používá aktuální lambdu s aktuálním stavem.

Podle Dokumentace Maps SDK pro Android (2025) Google doporučuje právě tento vzor při integraci Maps s Jetpack Compose. Podobný přístup se používá pro WebView, VideoView, TextureView a jakékoli další View-based komponenty, které přijímají callbacky přes set-metody. SideEffect zaručuje aktuálnost callbacků při každé změně stavu.

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 })
}

Synchronizace s Analytics SDK

Další důležitý scénář SideEffect — odesílání událostí do analytických systémů při změně stavu UI. Například když uživatel přepíná karty v TabLayout uvnitř obrazovky Compose, SideEffect může předávat aktuální stav vybrané karty do Firebase Analytics nebo AppsFlyer. Pokaždé, když se vybraná karta změní (a dojde k rekompozici), SideEffect odešle odpovídající událost.

Rozdíl oproti odesílání událostí přímo v onClick nebo onTabSelected je v tom, že SideEffect reaguje na změnu stavu vyvolanou jakýmkoli způsobem — nejen akcí uživatele, ale také programovou změnou, obnovením stavu po otočení obrazovky nebo Deep Link. To činí SideEffect univerzálním mechanismem synchronizace, nezávislým na zdroji změny.

Podle Firebase Best Practices (Google, 2025) poskytuje odesílání analytických událostí přes SideEffect úplnější obraz cesty uživatele, protože zaznamenává všechny změny stavu, včetně těch, které nastanou bez přímé akce uživatele. Je však důležité to nepřehánět: každá událost v Analytics je síťový požadavek, takže pro často se měnící stavy (pozice scrollování, souřadnice prstu) není SideEffect vhodný — použijte debounce nebo odesílejte události pouze při významných změnách.

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 s TabRow a vybranou kartou
}

SideEffect vs LaunchedEffect: kdy co použít

Výběr mezi SideEffect a LaunchedEffect závisí na dvou faktorech: zda je potřeba asynchronita a zda je potřeba řízení pomocí klíčů. SideEffect je synchronní a provádí se při každé rekompozici. LaunchedEffect je asynchronní (korutina) a provádí se pouze při změně klíče, ne při každé rekompozici.

Pokud potřebujete provést akci při každé změně UI — použijte SideEffect. Pokud potřebujete provést akci jednou při zobrazení obrazovky nebo při změně konkrétního parametru — použijte LaunchedEffect s klíči. Pokud je vyžadována asynchronní operace (načítání dat, zpoždění, práce s Flow) — pouze LaunchedEffect, protože SideEffect nepodporuje suspend funkce.

VlastnostSideEffectLaunchedEffect
ProváděníPři každé rekompoziciPři změně klíčů
AsynchronitaSynchronníKorutina
KlíčeNeAno (vararg)
ČištěníNeAutomatické zrušení korutiny
Typické použitíCallbacky, Analytics, synchronizace ViewNačítání, Flow předplatné, časovače

V praxi 70% případů použití side effects pokrývá LaunchedEffect (asynchronní operace, načítání dat), 20% DisposableEffect (zdroje s čištěním) a pouze 10% SideEffect (synchronizace callback funkcí). SideEffect je specializovaný nástroj pro úzký okruh úkolů, ale v těchto úkolech je nenahraditelný.

Typické chyby se SideEffect

Hlavní chyba — změna stavu Compose uvnitř SideEffect. Ačkoli SideEffect přímo nezpůsobuje nekonečnou smyčku (protože se provádí po fázi kompozice), může způsobit nadměrné rekompozice. Pokud se uvnitř SideEffect změní stav (mutableStateOf), spustí to novou rekompozici v dalším snímku, která znovu provede SideEffect — a tak dále až do stabilizace. Není to nekonečná smyčka, ale práce navíc pro framework.

Druhá chyba — provádění těžkých výpočtů uvnitř SideEffect. Protože se SideEffect volá při každé rekompozici a rekompozice mohou probíhat desetkrát za sekundu (při animacích, scrollování), jakýkoli těžký kód uvnitř SideEffect povede ke ztrátě snímků. Přesuňte těžké operace mimo kompozici — do korutiny (LaunchedEffect) nebo počítejte přes derivedStateOf / remember.

Třetí chyba — pokus o použití SideEffect pro asynchronní kód. SideEffect není suspend funkce, takže delay(), await(), collect() a další korutinové operace uvnitř něj se nezkompilují. Pokud potřebujete provést asynchronní akci po rekompozici, použijte snapshotFlow { ... } v kombinaci s LaunchedEffect nebo spusťte korutinu přes rememberCoroutineScope.

Často kladené otázky

Provádí se SideEffect při první kompozici?

Ano, SideEffect se provádí při každé úspěšné kompozici, včetně té první — když se komponenta poprvé objeví na obrazovce. To jej odlišuje od LaunchedEffect(Unit), který se také provádí jednou při první kompozici, ale neprovádí se při následujících rekompozicích (pokud se klíč nezměnil).

Může SideEffect způsobit nekonečnou smyčku?

Ne, SideEffect se provádí po fázi kompozice — změny provedené uvnitř něj budou aplikovány až v dalším snímku, což zabraňuje smyčkám. Častá změna stavu uvnitř SideEffect však může způsobit lavinu rekompozic, snižující výkon. Měňte stav uvnitř SideEffect pouze tehdy, když je to skutečně nutné.

Jaký je rozdíl mezi SideEffect a snapshotFlow?

SideEffect se provádí synchronně při každé rekompozici. snapshotFlow vytváří Flow ze stavu Compose a lze jej použít s collectLatest v LaunchedEffect pro reaktivní zpracování změn. snapshotFlow je vhodný pro případy, kdy potřebujete reagovat na změny s debounce, filter nebo distinctUntilChanged — což je v synchronním SideEffect nemožné.

Jak ladit SideEffect, pokud se provádí příliš často?

Použijte Android Studio Compose Modifier Debugger nebo přidejte logování s názvem komponenty a frekvencí volání. Pokud se SideEffect provádí častěji, než se očekávalo, zkontrolujte, zda se stav rodičovské komponenty nemění zbytečně. Optimalizace: izolujte stabilní části UI do samostatných composable funkcí s unstable anotacemi pro snížení počtu rekompozic.

Lze kombinovat SideEffect s DisposableEffect?

Ano, lze je použít v jedné komponentě pro různé účely. DisposableEffect se stará o nastavení a čištění zdroje (jednou) a SideEffect — o synchronizaci aktuálního stavu s tímto zdrojem při každé rekompozici. Typický příklad: DisposableEffect registruje callback přes API a SideEffect aktualizuje uzavřená data v tomto callbacku při každé změně.

Shrnutí

  • SideEffect — side-effect API pro synchronní kód prováděný po každé úspěšné rekompozici v Jetpack Compose.
  • Synchronizace — hlavní scénář: předávání stavu Compose externím systémům (Google Maps, WebView, ViewPager, Analytics SDK).
  • Callbacky — SideEffect zaručuje aktuálnost callback funkcí uzavírajících aktuální stav při každé aktualizaci UI.
  • Bez smyček — provádí se po fázi kompozice, takže změna stavu uvnitř SideEffect nezpůsobuje nekonečnou rekompozici.
  • Omezení — nepodporuje klíče, asynchronitu a blok čištění; pro tyto úkoly použijte LaunchedEffect nebo DisposableEffect.
  • Výkon — vyhněte se těžkým výpočtům uvnitř SideEffect, protože se provádí při každé rekompozici (až 60krát za sekundu).
  • Ladění — kontrolujte frekvenci volání přes Compose Debugger a optimalizujte přes remember pro filtrování zbytečných rekompozic.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také