State Hoisting — egy mintázat a Jetpack Compose-ban, ahol az állapot a gyermek Composable függvényből a szülőbe kerül áthelyezésre, és a gyermek paramétereken keresztül kapja meg az adatokat, és callback-eken keresztül értesít a változásokról. Ez az egyirányú adatáramlás (UDF) elvének megvalósítása, ahol az állapot felfelé emelkedik, az események pedig lefelé haladnak. A Google Android Developers, 2026 szerint a State Hoisting újrafelhasználhatóvá, tesztelhetővé és kiszámíthatóvá teszi a komponenseket.
Főbb pontok
State Hoisting (állapot emelés) — egy mintázat, ahol a Composable függvény nem birtokolja az állapotot, hanem külről kapja. A függvényen belüli var helyett két paramétert használunk: a megjelenítésre szánt értéket és egy lambda-callback-et a változások kezeléséhez. Technikailag ez azt jelenti, hogy a gyermek komponens statelessé válik (nincs saját állapota), a szülő pedig stateful (birtokolja az állapotot).
Példa: a Material3-ból származó TextField komponens nem tárolja a beírt szöveget magában. Elfogadja a value: String és onValueChange: (String) -> Unit paramétereket. A TextField-et meghívó szülő deklarálja a var value by remember { mutableStateOf("") }-t, és átadja a value-t és onValueChange-t. Ez klasszikus State Hoisting: TextField — egyszerű komponens (csak megjelenít és jelent a bevitelről), a szülő — okos (birtokolja az állapotot).
Stateless vs Stateful: A Stateless komponenst könnyebb tesztelni — nem függ a belső állapottól, viselkedését teljes mértékben a bemeneti paraméterek határozzák meg. A Stateful komponens kényelmes a gyors prototípuskészítéshez, de nehezebb újrafelhasználni — szorosan kötődik egy adatforráshoz. A State Hoisting választást ad: bármely komponens statelessé tehető az állapot felfelé helyezésével.
UDF (Unidirectional Data Flow) — egy architekturális elv, ahol az adatok egy irányba mozognak: az igazság forrásától (ViewModel vagy szülő Composable) az UI felé, az események pedig az ellenkező irányba. A State Hoisting az UDF megvalósítása az egyes komponensek szintjén. Ahelyett, hogy minden komponens maga döntené el, mikor és hogyan változtassa meg állapotát, jelenti az eseményt a szülőnek, és a szülő dönt az állapot megváltoztatásáról.
Az UDF előnyei: kiszámíthatóság — az állapot csak egy helyen változik, ami kiküszöböli a race condition-okat; nyomon követhetőség — a hívási veremből rekonstruálható a változások lánca; tesztelés — a stateful logika külön osztályba helyezhető és UI nélkül tesztelhető. Nagy projektekben az UDF és State Hoisting kombinációja de facto szabvány.
Egyetlen igazságforrás (Single Source of Truth) — egy másik elv, amely az UDF-hez társul. Az állapot minden részének pontosan egy forrása van. Ha két komponens ugyanazt az állapotot használja, a forrásnak közösnek kell lennie (ViewModel vagy közös szülő szintjén). A State Hoisting garantálja, hogy a forrás magasabban van a hierarchiában, és nem történik állapot duplicate.
| Irány | Mi kerül átadásra | Hogyan van megvalósítva |
|---|---|---|
| Lefelé (szülő → gyermek) | Megjelenítendő érték | Paraméter value: T |
| Felfelé (gyermek → szülő) | Változási esemény | Paraméter onValueChange: (T) -> Unit |
Fő szabály: az állapotot a lehető legalacsonyabb szintre kell emelni, amely elegendő az összes komponens számára, amelyeknek szükségük van rá. Ha az állapotot csak egy komponensen belül használják — hagyja lokális. Ha két szomszédos komponensnek ugyanarra az állapotra van szüksége — emelje a közös szülőbe. Ha az állapot az egész képernyőn szükséges — emelje a ViewModel-be.
A minimális emelés szabálya megakadályozza a szükségtelen bonyolultságot. Nincs értelme egy szöveges mező állapotát a ViewModel-be emelni, ha azt csak egy képernyőn belül használják, és nem marad meg az Activity újralétrehozásakor. Használja a rememberSaveable-t a képernyő szülő szintjén, ne a ViewModel-t, olyan UI állapothoz, amelynek túl kell élnie a képernyő forgatását, de nem szükséges az üzleti logikának.
Mikor emeljünk a ViewModel-be: ha az állapotnak meg kell maradnia az Activity újralétrehozásakor, ha több képernyőnek van szüksége rá, ha az állapotváltozás üzleti logikát indít el (hálózati kérések, adatbázis). A ViewModel szintű State Hoisting tipikus mintázat az MVVM architektúrában, ahol az UI réteg stateless, a ViewModel pedig stateful.
// ❌ Rossz: a komponens saját állapottal rendelkezik
@Composable
fun BadTextField(label: String) {
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}
// ✅ Jó: State Hoisting — állapot a szülőben
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}
// Használat: a szülő birtokolja az állapotot
@Composable
fun Form() {
var name by rememberSaveable { mutableStateOf("") }
GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}
Tekintsük a bejelentkezési képernyőt, amely két mezővel (email, jelszó) és egy gombbal rendelkezik. Mindhárom komponens állapotot kap a State Hoisting-en keresztül: az email és a jelszó kezelését a szülő végzi, a gomb az enabled állapotot értékként kapja.
// State Hoisting képernyő szinten
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
val uiState by viewModel.uiState.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
// Email mező — State Hoisting lambdán keresztül
EmailField(
email = uiState.email,
onEmailChange = { viewModel.onEmailChanged(it) }
)
// Jelszó mező — hasonlóan
PasswordField(
password = uiState.password,
onPasswordChange = { viewModel.onPasswordChanged(it) }
)
// Gomb — csak enabled-et kap (read-only)
LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
}
}
// Stateless komponens: email + callback fogadása
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
OutlinedTextField(
value = email,
onValueChange = onEmailChange,
label = { Text("Email") },
singleLine = true
)
}
// Stateless gomb komponens
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
Button(onClick = onClick, enabled = enabled) {
Text("Bejelentkezés")
}
}
EmailField és PasswordField — teljesen stateless. Bármely képernyőn újrafelhasználhatók, bármilyen adatforráshoz csatlakoztatva. A LoginButton az enabled-et read-only-ként kapja — ez a State Hoisting egy másik formája, ahol az állapot nem emelkedik (a gomb nem tudja magát engedélyezni), hanem készen kerül átadásra. Ez a megközelítés maximális rugalmasságot biztosít minimális komponens összefűzéssel.
Nem minden állapotot kell emelni. A lokális állapot (State a Composable-en belül) indokolt, amikor: az adatok csak egy komponensen belül szükségesek, nem befolyásolják a szomszédos elemeket, nem kell túlélniük egy adott szakasz recompozícióját. Például, animációs állapot, beviteli mező fókusza, aktuális görgetési pozíció — érdemes lokálisan tartani.
Mikor szükséges a State Hoisting: az állapotot több gyermek komponens használja; az egyik gyermekben történő változásnak tükröződnie kell a másikban; tesztelni kell az állapotváltozás logikáját az UI-tól elkülönítve; az állapotnak meg kell maradnia az Activity újralétrehozásakor. Ezekben az esetekben a lokális állapot adatok duplázódását és inkonzisztenciáját okozza.
Hibrid megközelítés: tartsa a minimális állapotot lokálisan, a többit emelje. A Compose szabálya: “emelje az állapotot olyan magasra, amennyire szükséges, és olyan alacsonyra, amennyire lehetséges”. A gyakorlatban ez azt jelenti, hogy kezdje a lokális remember-rel, és csak amikor felmerül az igény egy másik komponensből történő hozzáférésre — emelje a szintet. Ne alkalmazza a State Hoisting-et megelőző jelleggel — ez szükségtelenül bonyolítja a kódot.
Gyakran Ismételt Kérdések
State Hoisting — egy mintázat az UI komponensek szintjén. ViewModel — egy architekturális réteg az üzleti logika számára. A State Hoisting emelheti az állapotot a szülő Composable szintjére, a képernyő szintjére vagy a ViewModel-be. ViewModel — a legmagasabb emelési pont az állapot számára, amelynek túl kell élnie az Activity újralétrehozását.
A stateless komponens egyszerű értékátadással tesztelhető. Hívja meg a Composable-t a szükséges paraméterekkel, és ellenőrizze a megjelenítést a ComposeTestRule segítségével. Az állapotváltozás tesztelése a szülő vagy ViewModel szintjén történik — az UI-tól elkülönítve. Ez jelentősen leegyszerűsíti a teszteket: nem kell szimulálni a recompozíciót a komponensen belül.
Igen, ez bevett gyakorlat. Ha egy komponensnek csak meg kell jelenítenie az adatokat anélkül, hogy módosíthassa azokat — adjon át State<T>-t (read-only). A komponens fel fog iratkozni a változásokra, de nem tudja azokat kezdeményezni. Ez erősíti az enkapszulációt és védi az adatokat a nemkívánatos mutációktól.
Mély átadáshoz használja a CompositionLocal-t vagy az átadást a szülő Composable paraméterein keresztül. Ha az állapot az egész képernyőn szükséges — helyezze át a ViewModel-be, és használja a collectAsState()-ot. Az 5+ szinten keresztüli átadás hibás architektúra jele; vizsgálja újra a komponenshierarchiát.
State Hoisting kis mértékben növelheti a recompozíciók számát, mivel a szülőben történő változás összes gyermeket recomponálhatja. Használja a derivedStateOf-ot a változások szűréséhez és a keys-t a LazyColumn-ben célzott frissítésekhez. A legtöbb forgatókönyvben a State Hoisting többletterhelése elhanyagolható a karbantarthatóság előnyeihez képest.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is