MutableState — är ett gränssnitt i Jetpack Compose som representerar en behållare för ett föränderligt observerbart värde. Det utgör grunden för Compose reaktiva system: varje gång värdet på MutableState ändras via setter, meddelar Compose Runtime alla läsande komponenter och startar omkomposition (recomposition). Enligt Google Android Developers, 2026 är förståelse av MutableState obligatorisk för korrekt arbete med tillstånd i deklarativt UI.
Huvudpunkter
MutableState — är ett gränssnitt från paketet androidx.compose.runtime som deklarerar en egenskap: override var value: T. Gettern returnerar det aktuella värdet, settern skriver ett nytt och meddelar Compose Runtime om ändringen. Gränssnittet ärver från State<T>, där value är skrivskyddad. En sådan tvåskiktsarkitektur möjliggör separering av åtkomst: komponenten som endast behöver läsa värdet får State<T>, och ägarkomponenten — MutableState<T>.
Standardimplementeringen av MutableState är den interna klassen SnapshotMutableStateImpl, som använder snapshot-mekanismen för att spåra ändringar. När value-settern anropas registrerar den aktuella snapshot skrivningen och markerar alla registrerade ObservedScope (observationsområden) som ogiltiga. Dessa områden (vanligtvis Composable-funktioner) kommer att omkomponeras i nästa bildruta. Hela processen sker synkront och utan blockering tack vare Lock-free snapshot-arkitekturen.
State vs MutableState: State — är ett skrivskyddat gränssnitt som används för komponenters publika API:er. När du deklarerar en parameter för en Composable-funktion som State<Int>, garanterar du att komponenten kan läsa men inte ändra tillståndet. MutableState används inuti ägarkomponenten. En sådan separering — en av grundpraxiserna i Compose — förhindrar obehöriga ändringar.
Hierarkin av tillståndsgränssnitt i Compose har flera nivåer. Högst upp — State<T> med skrivskyddad value. Därunder — MutableState<T> med läs-skrivbar value. Därefter kommer specialiserade primitiva versioner: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState och andra, som undviker automatisk boxning av primitiver.
MutableDoubleState och MutableLongState — mindre vanliga men existerande typer. Samlingsgränssnitt: MutableListState — för att spåra ändringar inom en lista, MutableStateMap — för kartor. Vart och ett av dessa gränssnitt är optimerat för ett specifikt scenario och utökar den grundläggande MutableState med ytterligare metoder för samlingshantering.
SnapshotStateList och SnapshotStateMap — är implementeringar av föränderliga listor och kartor kompatibla med snapshots. De möjliggör spårning inte bara av värdesubstitution, utan även av interna ändringar: tillägg av ett element i listan, borttagning, ändring av ett befintligt element. För sådana strukturer skapar mutableStateListOf() och mutableStateMapOf() motsvarande observerbara samlingar.
| Gränssnitt | Syfte | Skapelsemetod |
|---|---|---|
| State<T> | Skrivskyddad behållare | — |
| MutableState<T> | Läs-skrivbar behållare | mutableStateOf() |
| MutableIntState | Primitiv Int utan boxning | mutableIntStateOf() |
| MutableFloatState | Primitiv Float utan boxning | mutableFloatStateOf() |
| SnapshotStateList | Observerbar lista | mutableStateListOf() |
| SnapshotStateMap | Observerbar karta | mutableStateMapOf() |
SnapshotMutationPolicy — är ett gränssnitt som bestämmer när en ändring av MutableState anses signifikant. mutableStateOf accepterar policy som andra argument. Standardimplementeringar: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (betraktar alltid ändring som signifikant). För egen logik kan du implementera din egen policy.
structuralEquality() — standardbeteende. Compose jämför det nya värdet med det gamla via equals(). Om resultatet är true — startas omkomposition INTE. Detta är bekvämt för primitiver och data classes, där två instanser med samma fält anses lika. Problem: om data class innehåller List utför equals() en djup jämförelse, vilket kan vara kostnadskrävande för stora listor.
referentialEquality() — jämför referenser via ===. Omkomposition startas endast vid tilldelning av ett annat objekt, även om innehållet är identiskt. Detta är optimalt för oföränderliga data classes, där varje ny instans garanterat innebär en ändring. neverEqualPolicy() — betraktar alltid ändring som signifikant utan att utföra jämförelse. Användbart när settern anropas sällan och man inte behöver lägga tid på equals.
// Policyjämförelse i praktiken
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: omkomposition ENDAST om data ändrades
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: omkomposition vid VARJE tilldelning
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() med samma fält utlöser INTE omkomposition
// user2: även user2.copy() == user2 utlöser omkomposition (ny ref)
}
Primitiva MutableIntState och liknande — är specialiserade gränssnitt som lagrar primitiver utan automatisk boxning. Vanlig MutableState<Int> lagrar Int som Integer, vilket vid varje skrivning skapar ett objekt på heapen. MutableIntState lagrar int (primitiv), vilket helt eliminerar boxningsomkostnader. Detta är särskilt viktigt vid högfrekventa uppdateringar — räknare, scrollpositioner, animationsvärden.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — funktioner som skapar primitiva MutableState. Gränssnitten heter MutableIntState, MutableFloatState, MutableLongState. De utökar MutableState<Int>, MutableState<Float> respektive MutableState<Long>, och lägger till egenskapen intValue som snabb åtkomst till primitiven. I deras interna implementering används AtomicInteger för lock-free läsning/skrivning.
Användning: räknare (Int), scrollpositioner (Float offset), tidsstämplar (Long). I de flesta vardagliga scenarier är prestandaskillnaden omärkbar, men i LazyList med tusentals element och övergångsanimationer ger primitiva State en märkbar förbättring. Google rekommenderar att använda primitiva State för typiska scenarier istället för universell mutableStateOf.
@Composable
fun ScrollCounter() {
// Dåligt: boxning vid varje uppdatering
var badCount by remember { mutableStateOf(0) }
// Bra: ingen boxning, primitiv lagring
var goodCount by remember { mutableIntStateOf(0) }
// Användning är identisk
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Låt oss titta på komponenten TodoList, där MutableState används i två former: som separata variabler för inmatningstillstånd och som SnapshotStateList för en dynamisk uppgiftslista. Båda använder delegering för kodkoncishet.
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)
@Composable
fun TodoScreen() {
var inputText by remember { mutableStateOf("") }
val items = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = inputText,
onValueChange = { inputText = it }
)
Button(onClick = {
if (inputText.isNotBlank()) {
items.add(TodoItem(items.size, inputText))
inputText = ""
}
}) { Text("Lägg till") }
}
LazyColumn {
items(items) { item ->
Row(modifier = Modifier.fillMaxWidth().clickable {
val idx = items.indexOf(item)
items[idx] = item.copy(isDone = !item.isDone)
}) {
Checkbox(checked = item.isDone, onCheckedChange = null)
Text(item.text)
}
}
}
}
}
mutableStateListOf skapar en SnapshotStateList — en föränderlig lista som spårar ändringar av enskilda element. Vid anrop av items.add() och items[n] = newValue ser Compose mutationen och omkomponerar endast de LazyColumn-element som har ändrats. inputText — en vanlig MutableState<String>. Kombinationen av två MutableState-typer (enskild och samling) — ett typiskt mönster för skärmar med formulär och listor.
Vanliga frågor
MutableState utan remember kommer att skapas på nytt vid varje omkomposition. Varje nytt anrop av mutableStateOf skapar ett nytt objekt och det gamla värdet går förlorat. Använd alltid remember för att bevara State mellan omkompositioner, om inte State skapas utanför Composable (t.ex. i ViewModel).
Läs .value en gång utanför snapshot via snapshot { }. Men detta stänger av reaktivitet — ändringar kommer inte längre att utlösa omkomposition. För engångsläsning utan prenumeration, använd currentValue() inuti snapshot utan läsning.
mutableIntStateOf är snabbare eftersom det inte kräver boxning av int till Integer. Vid tusentals uppdateringar per sekund (animation, scroll) kan skillnaden nå 30-50% av allokeringstiden. Vid sällsynta uppdateringar (klick, textinmatning) är skillnaden försumbar.
Det kan, men rekommenderas inte. Istället för MutableState, skicka State (skrivskyddad) + lambda onValueChange. Detta implementerar State Hoisting-mönstret och gör komponenten återanvändbar. Komponenter som accepterar MutableState bryter mot envägs dataflöde.
Implementera MutableState-gränssnittet och tillhandahåll override var value med getter och setter. I settern kan du lägga till validering eller loggning. För bakåtkompatibilitet med Compose Runtime, slå in din implementering i snapshotFlow eller använd snapshotIncrement.
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å