State Hoisting — är ett mönster i Jetpack Compose där tillståndet flyttas ut från den underordnade Composable-funktionen till den överordnade, och den underordnade får data via parametrar och meddelar om ändringar via callbacks. Detta är en implementering av principen om enkelriktat dataflöde (UDF), där tillståndet lyfts uppåt och händelser går neråt. Enligt Google Android Developers, 2026 gör State Hoisting komponenter återanvändbara, testbara och förutsägbara.
Huvudpunkter
State Hoisting (lyfta tillstånd) — är ett mönster där en Composable-funktion inte äger tillståndet utan tar emot det utifrån. Istället för var inuti funktionen används två parametrar: värdet för visning och en lambda-callback för att hantera ändringar. Tekniskt innebär detta att den underordnade komponenten blir stateless (har inget eget tillstånd) och den överordnade blir stateful (äger tillståndet).
Exempel: TextField-komponenten från Material3 lagrar inte den angivna texten inuti sig. Den tar emot value: String och onValueChange: (String) -> Unit. Den överordnade som anropar TextField deklarerar var value by remember { mutableStateOf("") } och skickar value och onValueChange. Detta är klassisk State Hoisting: TextField — enkel komponent (visar bara och rapporterar inmatning), den överordnade — smart (äger tillståndet).
Stateless vs Stateful: En Stateless-komponent är lättare att testa — den beror inte på internt tillstånd, dess beteende bestäms helt av indataparametrar. En Stateful-komponent är praktisk för snabb prototypframställning men svårare att återanvända — den är hårt bunden till en enda datakälla. State Hoisting ger valmöjlighet: vilken komponent som helst kan göras stateless genom att flytta tillståndet uppåt.
UDF (Unidirectional Data Flow) — är en arkitektonisk princip där data rör sig i en riktning: från sanningskällan (ViewModel eller överordnad Composable) till UI, och händelser i motsatt riktning. State Hoisting är implementeringen av UDF på nivån av enskilda komponenter. Istället för att varje komponent själv bestämmer när och hur den ska ändra sitt tillstånd, rapporterar den händelsen till den överordnade, och den överordnade bestämmer hur tillståndet ska ändras.
Fördelar med UDF: förutsägbarhet — tillstånd ändras bara på ett ställe, vilket eliminerar race conditions; spårbarhet — via anropsstacken kan ändringskedjan rekonstrueras; testning — stateful-logik kan flyttas till en separat klass och testas utan UI. I stora projekt är UDF i kombination med State Hoisting de facto-standard.
En enda sanningskälla (Single Source of Truth) — ytterligare en princip som följer UDF. Varje tillståndsfragment har exakt en källa. Om två komponenter använder samma tillstånd måste källan vara gemensam (på ViewModel-nivå eller gemensam överordnad). State Hoisting garanterar att källan är högre upp i hierarkin och att ingen duplicering av tillstånd sker.
| Riktning | Vad som överförs | Hur det är implementerat |
|---|---|---|
| Neråt (överordnad → underordnad) | Värde för visning | Parameter value: T |
| Uppåt (underordnad → överordnad) | Ändringshändelse | Parameter onValueChange: (T) -> Unit |
Huvudregel: tillståndet bör lyftas till lägsta möjliga nivå som är tillräcklig för alla komponenter som behöver det. Om tillståndet bara används inuti en komponent — behåll det lokalt. Om två närliggande komponenter behöver samma tillstånd — lyft det till den gemensamma överordnade. Om tillståndet behövs på hela skärmen — lyft det till ViewModel.
Regeln om minimal lyftning förhindrar onödig komplexitet. Det är meningslöst att lyfta tillståndet för ett textfält till ViewModel om det bara används inuti en skärm och inte sparas när Activity återskapas. Använd rememberSaveable på skärmens överordnadsnivå, inte ViewModel, för UI-tillstånd som måste överleva skärmrotation men inte behövs av affärslogik.
När ska man lyfta till ViewModel: om tillståndet måste bevaras när Activity återskapas, om det behövs av flera skärmar, om tillståndsändring utlöser affärslogik (nätverksförfrågningar, databas). State Hoisting på ViewModel-nivå är ett typiskt mönster i MVVM-arkitektur, där UI-lagret är stateless och ViewModel är stateful.
// ❌ Dåligt: komponenten äger sitt eget tillstånd
@Composable
fun BadTextField(label: String) {
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}
// ✅ Bra: State Hoisting — tillstånd i överordnad
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}
// Användning: den överordnade äger tillståndet
@Composable
fun Form() {
var name by rememberSaveable { mutableStateOf("") }
GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}
Betrakta inloggningsskärmen med två fält (e-post, lösenord) och en knapp. Alla tre komponenter får tillstånd via State Hoisting: e-post och lösenord hanteras av den överordnade, knappen får enabled-status som ett värde.
// State Hoisting på skärmnivå
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
val uiState by viewModel.uiState.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
// E-postfält — State Hoisting via lambda
EmailField(
email = uiState.email,
onEmailChange = { viewModel.onEmailChanged(it) }
)
// Lösenordsfält — liknande
PasswordField(
password = uiState.password,
onPasswordChange = { viewModel.onPasswordChanged(it) }
)
// Knapp — får bara enabled (read-only)
LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
}
}
// Stateless komponent: tar emot e-post + callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
OutlinedTextField(
value = email,
onValueChange = onEmailChange,
label = { Text("E-post") },
singleLine = true
)
}
// Stateless knappkomponent
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
Button(onClick = onClick, enabled = enabled) {
Text("Logga in")
}
}
EmailField och PasswordField — helt stateless. De kan återanvändas på vilken skärm som helst, anslutna till valfri datakälla. LoginButton får enabled som read-only — detta är en annan form av State Hoisting där tillståndet inte lyfts (knappen kan inte aktivera sig själv), utan skickas färdigt. Detta tillvägagångssätt ger maximal flexibilitet med minimal komponentkoppling.
Inte varje tillstånd behöver lyftas. Lokalt tillstånd (State inuti Composable) är berättigat när: data bara behövs inuti en komponent, de påverkar inte närliggande element, de behöver inte överleva rekomposition av en specifik sektion. Till exempel, animationstillstånd, fokus på inmatningsfält, aktuell scrollposition — det är förnuftigt att behålla lokalt.
När är State Hoisting nödvändigt: tillstånd används av flera underordnade komponenter; en ändring i en underordnad måste speglas i en annan; logiken för tillståndsändring måste testas separat från UI; tillstånd måste bevaras när Activity återskapas. I dessa fall skapar lokalt tillstånd duplicering och inkonsekvens av data.
Hybridmetod: behåll minimalt tillstånd lokalt, lyft resten. Compose-regeln: "lyft tillståndet så högt som nödvändigt och så lågt som möjligt". I praktiken innebär detta att börja med lokalt remember, och endast när behov av åtkomst från en annan komponent uppstår — lyft nivån. Gör inte State Hoisting preventivt — det komplicerar koden i onödan.
Vanliga frågor
State Hoisting — är ett mönster på UI-komponentnivå. ViewModel — är en arkitektonisk nivå för affärslogik. State Hoisting kan lyfta tillstånd till nivån för den överordnade Composable, till skärmnivå eller till ViewModel. ViewModel — är den högsta lyftpunkten för tillstånd som måste överleva återskapande av Activity.
En stateless komponent testas genom att enkelt skicka värden. Anropa Composable med nödvändiga parametrar och kontrollera visningen via ComposeTestRule. Tillståndsändring testas på nivån av den överordnade eller ViewModel — separat från UI. Detta förenklar testerna avsevärt: ingen simulering av rekomposition inuti komponenten behövs.
Ja, det är vanlig praxis. Om en komponent bara behöver visa data utan att kunna ändra dem — skicka State<T> (read-only). Komponenten kommer att prenumerera på ändringar men kan inte initiera dem. Detta stärker inkapslingen och skyddar data från oönskade mutationer.
För djup överföring, använd CompositionLocal eller överföring via parametrar för den överordnade Composable. Om tillståndet behövs på hela skärmen — flytta det till ViewModel och använd collectAsState(). Överföring via 5+ nivåer är tecken på felaktig arkitektur; ompröva komponenthierarkin.
State Hoisting kan något öka antalet rekompositioner, eftersom en ändring i den överordnade kan omkomponera alla underordnade. Använd derivedStateOf för att filtrera ändringar och keys i LazyColumn för riktade uppdateringar. I de flesta scenarier är overhead för State Hoisting försumbar jämfört med fördelarna med underhållbarhet.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också