MutableState — je rozhraní v Jetpack Compose, které představuje kontejner pro měnitelnou pozorovatelnou hodnotu. Je základem reaktivního systému Compose: pokaždé, když se hodnota MutableState změní přes setter, Compose Runtime upozorní všechny čtoucí komponenty a spustí rekompozici. Podle Google Android Developers, 2026 je porozumění MutableState povinné pro správnou práci se stavem v deklarativním UI.
Hlavní body
MutableState — je rozhraní z balíčku androidx.compose.runtime, které deklaruje jednu vlastnost: override var value: T. Getter vrací aktuální hodnotu, setter zapisuje novou a informuje Compose Runtime o změně. Rozhraní dědí od State<T>, kde je value přístupný pouze pro čtení. Taková dvouvrstvá architektura umožňuje oddělení přístupu: komponenta, která potřebuje pouze číst hodnotu, dostává State<T>, a komponenta-vlastník — MutableState<T>.
Výchozí implementací MutableState je vnitřní třída SnapshotMutableStateImpl, která využívá mechanismus snapshotů ke sledování změn. Když je setter value zavolán, aktuální snapshot zaznamená zápis a označí všechny registrované ObservedScope (oblasti pozorování) jako neplatné. Tyto oblasti (obvykle Composable funkce) budou rekomponovány v dalším snímku. Celý proces probíhá synchronně a bez blokování díky architektuře Lock-free snapshot.
State vs MutableState: State — je rozhraní pouze pro čtení, používané pro veřejná API komponent. Když deklarujete parametr Composable funkce jako State<Int>, garantujete, že komponenta může číst, ale neměnit stav. MutableState se používá uvnitř komponenty-vlastníka. Takové oddělení — jeden ze základních postupů Compose — zabraňuje neautorizovaným změnám.
Hierarchie rozhraní stavu v Compose má několik úrovní. Na vrcholu — State<T> s value pouze pro čtení. Níže — MutableState<T> s value pro čtení i zápis. Dále následují specializované primitivní verze: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState a další, které se vyhýbají autoboxingu primitiv.
MutableDoubleState a MutableLongState — méně rozšířené, ale existující typy. Rozhraní kolekcí: MutableListState — pro sledování změn uvnitř seznamu, MutableStateMap — pro mapy. Každé z těchto rozhraní je optimalizováno pro konkrétní scénář a rozšiřuje základní MutableState o další metody pro práci s kolekcí.
SnapshotStateList a SnapshotStateMap — jsou implementace měnitelných seznamů a map kompatibilní se snapshoty. Umožňují sledovat nejen nahrazení hodnoty, ale i vnitřní změny: přidání prvku do seznamu, smazání, změnu existujícího prvku. Pro takové struktury vytvářejí mutableStateListOf() a mutableStateMapOf() odpovídající pozorovatelné kolekce.
| Rozhraní | Účel | Metoda vytvoření |
|---|---|---|
| State<T> | Kontejner pouze pro čtení | — |
| MutableState<T> | Kontejner pro čtení a zápis | mutableStateOf() |
| MutableIntState | Primitivní Int bez boxingu | mutableIntStateOf() |
| MutableFloatState | Primitivní Float bez boxingu | mutableFloatStateOf() |
| SnapshotStateList | Pozorovatelný seznam | mutableStateListOf() |
| SnapshotStateMap | Pozorovatelná mapa | mutableStateMapOf() |
SnapshotMutationPolicy — je rozhraní určující, kdy je změna MutableState považována za významnou. mutableStateOf přijímá policy jako druhý argument. Standardní implementace: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (vždy považuje změnu za významnou). Pro vlastní logiku můžete implementovat svůj vlastní policy.
structuralEquality() — výchozí chování. Compose porovnává novou hodnotu se starou přes equals(). Pokud je výsledek true — rekompozice se NESPOUŠTÍ. To je výhodné pro primitivy a data classy, kde jsou dvě instance se stejnými poli považovány za stejné. Problém: pokud data class obsahuje List, equals() provádí hluboké porovnání, což může být nákladné pro velké seznamy.
referentialEquality() — porovnává reference přes ===. Rekompozice se spouští pouze při přiřazení jiného objektu, i když je obsah identický. To je optimální pro neměnné data classy, kde každá nová instance zaručeně znamená změnu. neverEqualPolicy() — vždy považuje změnu za významnou, aniž by prováděla porovnání. Užitečné, když je setter volán zřídka a není třeba ztrácet čas equals.
// Porovnání politik v praxi
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: rekompozice POUZE pokud se data změnila
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: rekompozice při KAŽDÉM přiřazení
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() se stejnými poli NESPOUŠTÍ rekompozici
// user2: i user2.copy() == user2 spouští rekompozici (nová ref)
}
Primitivní MutableIntState a analogy — jsou specializovaná rozhraní, která ukládají primitivy bez autoboxingu. Běžný MutableState<Int> ukládá Int jako Integer, což při každém zápisu vytváří objekt na haldě. MutableIntState ukládá int (primitiv), zcela eliminující režii balení. To je důležité zejména při vysokofrekvenčních aktualizacích — čítače, pozice posouvání, animační hodnoty.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — funkce vytvářející primitivní MutableState. Rozhraní se nazývají MutableIntState, MutableFloatState, MutableLongState. Rozšiřují MutableState<Int>, MutableState<Float> a MutableState<Long> příslušně, přidávají vlastnost intValue jako rychlý přístup k primitivu. V jejich vnitřní implementaci se používá AtomicInteger pro lock-free čtení/zápis.
Použití: čítače (Int), pozice posouvání (Float offset), časová razítka (Long). Ve většině každodenních scénářů je rozdíl ve výkonu nepostřehnutelný, ale v LazyList s tisíci prvky a přechodovými animacemi poskytují primitivní State znatelné zlepšení. Google doporučuje používat primitivní State pro typické scénáře místo univerzálního mutableStateOf.
@Composable
fun ScrollCounter() {
// Špatně: balení při každé aktualizaci
var badCount by remember { mutableStateOf(0) }
// Dobře: žádné balení, primitivní úložiště
var goodCount by remember { mutableIntStateOf(0) }
// Použití je identické
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Podívejme se na komponentu TodoList, kde je MutableState použit ve dvou formách: jako samostatné proměnné pro stav vstupu a jako SnapshotStateList pro dynamický seznam úkolů. Obě používají delegování pro stručnost kódu.
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("Přidat") }
}
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 vytváří SnapshotStateList — měnitelný seznam, který sleduje změny jednotlivých prvků. Při volání items.add() a items[n] = newValue Compose vidí mutaci a rekomponuje pouze ty prvky LazyColumn, které se změnily. inputText — běžný MutableState<String>. Kombinace dvou typů MutableState (jednotlivý a kolekce) — typický vzor pro obrazovky s formuláři a seznamy.
Často kladené otázky
MutableState bez remember bude při každé rekompozici vytvořen znovu. Každé nové volání mutableStateOf vytváří nový objekt a stará hodnota se ztrácí. Vždy používejte remember pro uchování State mezi rekompozicemi, pokud není State vytvořen mimo Composable (např. ve ViewModelu).
Přečtěte .value jednou mimo snapshot přes snapshot { }. To však vypíná reaktivitu — změny již nebudou spouštět rekompozici. Pro jednorázové čtení bez odběru použijte currentValue() uvnitř snapshot bez čtení.
mutableIntStateOf je rychlejší, protože nevyžaduje balení int do Integer. Při tisících aktualizací za sekundu (animace, posouvání) může rozdíl dosáhnout 30-50 % času alokace. Při vzácných aktualizacích (kliknutí, zadávání textu) je rozdíl zanedbatelný.
Lze, ale nedoporučuje se. Místo MutableState předejte State (pouze pro čtení) + lambdu onValueChange. To implementuje vzor State Hoisting a činí komponentu znovupoužitelnou. Komponenty přijímající MutableState porušují jednosměrný tok dat.
Implementujte rozhraní MutableState a poskytněte override var value s getterem a setterem. V setteru můžete přidat validaci nebo logování. Pro zpětnou kompatibilitu s Compose Runtime zabalte svou implementaci do snapshotFlow nebo použijte snapshotIncrement.
Shrnutí
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í.
Přečtěte si také