State Hoisting — este un pattern în Jetpack Compose prin care starea este extrasă din funcția Composable copil în cea părinte, iar copilul primește datele prin parametri și notifică modificările prin callback-uri. Aceasta este o implementare a principiului fluxului unidirecțional de date (UDF), în care starea este ridicată în sus, iar evenimentele coboară în jos. Potrivit Google Android Developers, 2026, State Hoisting face componentele reutilizabile, testabile și predictibile.
Principalele puncte
State Hoisting (ridicarea stării) — este un pattern în care funcția Composable nu deține starea, ci o primește din exterior. În loc de var în interiorul funcției, se folosesc doi parametri: valoarea pentru afișare și un lambda-callback pentru gestionarea modificărilor. Tehnic, aceasta înseamnă că componenta copil devine stateless (nu are stare proprie), iar părintele devine stateful (deține starea).
Exemplu: componenta TextField din Material3 nu stochează textul introdus în interiorul său. Aceasta primește value: String și onValueChange: (String) -> Unit. Părintele care apelează TextField declară var value by remember { mutableStateOf("") } și transmite value și onValueChange. Acesta este un State Hoisting clasic: TextField — componentă simplă (doar afișează și raportează introducerea), părintele — inteligent (deține starea).
Stateless vs Stateful: O componentă Stateless este mai ușor de testat — nu depinde de starea internă, comportamentul său este complet determinat de parametrii de intrare. O componentă Stateful este convenabilă pentru prototipare rapidă, dar mai dificil de reutilizat — este strict legată de o singură sursă de date. State Hoisting oferă alegerea: orice componentă poate fi făcută stateless prin mutarea stării în sus.
UDF (Unidirectional Data Flow) — este un principiu arhitectural în care datele se mișcă într-o singură direcție: de la sursa de adevăr (ViewModel sau părintele Composable) către UI, iar evenimentele în direcția opusă. State Hoisting este implementarea UDF la nivelul componentelor individuale. În loc ca fiecare componentă să decidă singură când și cum să-și schimbe starea, aceasta raportează evenimentul părintelui, iar părintele decide cum să modifice starea.
Avantajele UDF: predictibilitate — starea se schimbă doar într-un singur loc, ceea ce elimină race conditions; traceability — prin stiva de apeluri se poate reconstitui lanțul de modificări; testare — logica stateful poate fi extrasă într-o clasă separată și testată fără UI. În proiectele mari, UDF combinat cu State Hoisting este un standard de facto.
Sursă unică de adevăr (Single Source of Truth) — un alt principiu care însoțește UDF. Fiecare fragment de stare are exact o sursă. Dacă două componente folosesc aceeași stare, sursa trebuie să fie comună (la nivel de ViewModel sau părinte comun). State Hoisting garantează că sursa se află mai sus în ierarhie și nu are loc duplicarea stării.
| Direcție | Ce se transmite | Cum este implementat |
|---|---|---|
| În jos (părinte → copil) | Valoarea pentru afișare | Parametru value: T |
| În sus (copil → părinte) | Evenimentul de modificare | Parametru onValueChange: (T) -> Unit |
Regula principală: starea trebuie ridicată la cel mai mic nivel posibil, suficient pentru toate componentele care au nevoie de ea. Dacă starea este folosită doar în interiorul unei singure componente — păstrați-o locală. Dacă două componente vecine au nevoie de aceeași stare — ridicați-o la părintele comun. Dacă starea este necesară pe întreg ecranul — ridicați-o în ViewModel.
Regula ridicării minime previne complexitatea inutilă. Nu are sens să ridicați starea unui câmp text în ViewModel dacă este folosită doar în interiorul unui singur ecran și nu se păstrează la recrearea Activity. Folosiți rememberSaveable la nivelul părintelui ecranului, nu ViewModel, pentru starea UI care trebuie să supraviețuiască rotației ecranului, dar nu este necesară logicii de afaceri.
Când să ridicați în ViewModel: dacă starea trebuie păstrată la recrearea Activity, dacă este necesară mai multor ecrane, dacă modificarea stării declanșează logică de afaceri (cereri de rețea, baze de date). State Hoisting la nivel de ViewModel este un pattern tipic în arhitectura MVVM, unde stratul UI este stateless, iar ViewModel este stateful.
// ❌ Rău: componenta deține propria stare
@Composable
fun BadTextField(label: String) {
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}
// ✅ Bine: State Hoisting — starea în părinte
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}
// Utilizare: părintele deține starea
@Composable
fun Form() {
var name by rememberSaveable { mutableStateOf("") }
GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}
Luați în considerare ecranul de autentificare care are două câmpuri (email, parolă) și un buton. Toate cele trei componente primesc starea prin State Hoisting: email și parola sunt gestionate de părinte, butonul primește statusul enabled ca valoare.
// State Hoisting la nivel de ecran
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
val uiState by viewModel.uiState.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
// Câmp Email — State Hoisting prin lambda
EmailField(
email = uiState.email,
onEmailChange = { viewModel.onEmailChanged(it) }
)
// Câmp parolă — similar
PasswordField(
password = uiState.password,
onPasswordChange = { viewModel.onPasswordChanged(it) }
)
// Buton — primește doar enabled (read-only)
LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
}
}
// Componentă stateless: primește email + callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
OutlinedTextField(
value = email,
onValueChange = onEmailChange,
label = { Text("Email") },
singleLine = true
)
}
// Componentă buton stateless
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
Button(onClick = onClick, enabled = enabled) {
Text("Autentificare")
}
}
EmailField și PasswordField — complet stateless. Ele pot fi reutilizate pe orice ecran, conectate la orice sursă de date. LoginButton primește enabled ca read-only — aceasta este o altă formă de State Hoisting în care starea nu este ridicată (butonul nu se poate activa singur), ci este transmisă gata făcută. Această abordare oferă maximă flexibilitate cu o cuplare minimă a componentelor.
Nu orice stare trebuie ridicată. Starea locală (State în interiorul Composable) este justificată atunci când: datele sunt necesare doar într-o singură componentă, nu afectează elementele vecine, nu trebuie să supraviețuiască recompoziției unei secțiuni specifice. De exemplu, starea animației, focusul câmpului de intrare, poziția curentă de scroll — este rezonabil să le păstrați local.
Când State Hoisting este necesar: starea este folosită de mai multe componente copil; modificarea într-un copil trebuie să se reflecte în altul; trebuie să testați logica de modificare a stării separat de UI; starea trebuie păstrată la recrearea Activity. În aceste cazuri, starea locală creează duplicare și inconsistență a datelor.
Abordarea hibridă: păstrați starea minimă local, ridicați restul. Regula Compose: „ridicați starea atât de sus cât este necesar și atât de jos cât este posibil”. În practică, aceasta înseamnă să începeți cu remember local și doar când apare nevoia de acces dintr-o altă componentă — să ridicați nivelul. Nu faceți State Hoisting preventiv — complică codul fără necesitate.
Întrebări frecvente
State Hoisting — este un pattern la nivelul componentelor UI. ViewModel — este un strat arhitectural pentru logica de afaceri. State Hoisting poate ridica starea la nivelul părintelui Composable, la nivelul ecranului sau în ViewModel. ViewModel — este cel mai înalt punct de ridicare pentru starea care trebuie să supraviețuiască recreerii Activity.
O componentă stateless se testează prin simpla transmitere a valorilor. Apelați Composable cu parametrii necesari și verificați afișarea prin ComposeTestRule. Modificarea stării se testează la nivelul părintelui sau ViewModel — separat de UI. Aceasta simplifică semnificativ testele: nu trebuie să simulați recompoziția în interiorul componentei.
Da, este o practică comună. Dacă o componentă trebuie doar să afișeze date fără posibilitatea de a le modifica — transmiteți State<T> (read-only). Componenta se va abona la modificări, dar nu va putea să le inițieze. Aceasta întărește encapsularea și protejează datele de mutații nedorite.
Pentru transmiterea profundă, folosiți CompositionLocal sau transmiterea prin parametrii părintelui Composable. Dacă starea este necesară pe întreg ecranul — extrageți-o în ViewModel și folosiți collectAsState(). Transmiterea prin 5+ niveluri este un semn de arhitectură incorectă; revizuiți ierarhia componentelor.
State Hoisting poate crește ușor numărul de recompoziții, deoarece o modificare în părinte poate recompona toți copiii. Folosiți derivedStateOf pentru filtrarea modificărilor și keys în LazyColumn pentru actualizări punctuale. În majoritatea scenariilor, overhead-ul State Hoisting este neglijabil în comparație cu beneficiile de întreținere.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și