mutableStateOf je funkce v Jetpack Compose, která vytváří kontejner měnitelného pozorovatelného stavu. Když se hodnota uvnitř tohoto kontejneru změní, Compose automaticky spustí rekompozici všech komponent, které tento stav čtou. Bez mutableStateOf by se UI nemohlo reaktivně aktualizovat při změnách dat. Podle Google Android Developers, 2026 je mutableStateOf základním stavebním blokem pro lokální stav v Compose.
Hlavní body
mutableStateOf je funkce z balíčku compose.runtime, která vytváří objekt MutableState<T> ukládající hodnotu a schopný upozorňovat Compose Runtime na změny. Signatura: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Parametr policy určuje, kdy je změna považována za významnou — při strukturální rovnosti, při referenční rovnosti nebo nikdy.
MutableState je rozhraní s jedinou vlastností value: getter pro čtení a setter pro zápis. Při volání setteru Compose Runtime zaznamená změnu v snapshotu a označí všechny funkce Composable čtoucí tuto proměnnou State jako vyžadující rekompozici. Tento proces probíhá synchronně v rámci jednoho snapshot cyklu, což eliminuje mezistavy při kaskádových změnách.
Parameter policy — druhý argument mutableStateOf, určující chování při porovnávání. structuralEquality() kontroluje equals() — toto je výchozí chování. referentialEquality() kontroluje === (referenční rovnost). neverEqual() považuje každé přiřazení za změnu. Volba policy ovlivňuje, zda se rekompozice spustí při přiřazení stejné hodnoty.
Nejjednodušší způsob, jak deklarovat pozorovatelný stav, je použít mutableStateOf s remember. Bez remember by se při každé rekompozici vytvářel nový State a všechny předchozí změny by byly ztraceny. remember zaručuje, že stejný MutableState přežije sérii rekompozic, dokud funkce Composable zůstává v kompozici.
@Composable
fun Counter() {
// Bez delegace: čtení/zápis přes .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// S delegací: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
Rozdíl mezi dvěma variantami je syntaktický. Property Delegation (by) používá konvenci Kotlin: kompilátor generuje volání getValue() a setValue() pro čtení a zápis. To je ekvivalentní přímému přístupu k count.value, ale vypadá jako práce s běžnou proměnnou. Obě varianty jsou funkčně identické: Compose sleduje čtení v getteru a zápis v setteru bez ohledu na formu zápisu.
| Forma | Kód | Čtení | Zápis |
|---|---|---|---|
| Bez delegace | val count = mutableStateOf(0) | count.value | count.value = n |
| S delegací | var count by mutableStateOf(0) | count | count = n |
Mechanismus delegovaných vlastností Kotlin není funkcí Compose, ale vestavěnou schopností jazyka. Jakákoli třída může implementovat operátory getValue(thisRef, property) a setValue(thisRef, property, value), poté může být její instance použita s klíčovým slovem by. MutableState funguje přesně takto: getValue vrací aktuální hodnotu a setValue přiřazuje novou.
Důležitý rozdíl: val a var. mutableStateOf lze přiřadit jak k val, tak k var. V případě val (val count = mutableStateOf(0)) je samotný objekt MutableState neměnný, ale jeho vlastnost value může být měněna. V případě var (var count by mutableStateOf(0)) delegace vytváří iluzi práce s primitivem, ale ve skutečnosti setter volá setValue na MutableState. Volba mezi val a var je volbou mezi explicitním a implicitním přístupem k .value.
State Delegation je syntaktický cukr, který zjednodušuje kód, ale nemění mechaniku. Kotlin kompilátor překládá var x by mutableStateOf(0) na getter/setter, které volají mutableStateOf.getValue() a mutableStateOf.setValue(). Ve vygenerovaném bytekódu není rozdíl mezi val a var s by — oba pracují přes stejný kontejner MutableState.
// Vlastní delegace pro Compose State
class ValidatedState<T>(initialValue: T) {
private val state = mutableStateOf(initialValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (value != state.value) {
state.value = value
}
}
}
@Composable
fun Test() {
var text by remember { ValidatedState("") }
}
Snapshot je mechanismus Compose Runtime zajišťující konzistenci čtení State při paralelních změnách. Když funkce Composable čte mutableStateOf, snapshot zaznamená aktuální hodnotu. Pokud během kompozice jiná změna zapíše do stejného State, snapshot zápis vidí, ale neumožňuje čtení nekonzistentních dat — čtení vždy vrací hodnotu aktuální v okamžiku zahájení snapshotu.
Při volání setteru mutableStateOf.value = newValue Compose Runtime nespouští rekompozici okamžitě. Místo toho je změna zaregistrována v aktuálním snapshotu. Když je snapshot aplikován (na hranici snímku), Compose prochází seznam změněných State a označuje čtoucí komponenty jako Invalid. Teprve v dalším snímku se spouští rekompozice. To zaručuje, že UI není překreslováno desítkykrát při kaskádových změnách.
Globální a lokální snapshoty: ve výchozím nastavení mutableStateOf pracuje v globálním snapshotu, který se aplikuje automaticky. Lze vytvořit lokální snapshot pomocí Snapshot.takeSnapshot() pro izolované čtení bez vedlejších účinků. To se používá uvnitř Modifier, kde je třeba číst State, ale není třeba se přihlásit k odběru změn. Tento přístup optimalizuje výkon a zabraňuje neočekávaným rekompozicím.
Podívejme se na reálný scénář — přihlašovací formulář se třemi poli: email, heslo a stav načítání. Všechna tři pole používají mutableStateOf, ale s různými policy a různým vnořením. email používá delegaci, heslo — přímý přístup.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// Jednotný State pro formulář, policy = referentialEquality
var formState by remember {
mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
}
val isValid = remember(formState) {
formState.email.contains("@") && formState.password.length() >= 6
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = formState.email,
onValueChange = { formState = formState.copy(email = it) },
label = { Text("Email") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("Heslo") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Přihlásit")
}
}
}
V tomto příkladu je mutableStateOf použito s vlastní datovou třídou LoginState a policy referentialEquality. To znamená, že rekompozice se spustí pouze při přiřazení nové instance LoginState přes copy(). isValid je vypočítáváno na základě formState a přepočítává se pouze při jeho změně. Tento přístup poskytuje jasnou kontrolu nad rekompozicemi: každé pole formuláře se mění pouze vytvořením nové kopie.
Často kladené otázky
mutableStateOf je kontejner specifický pro Compose, pracující uvnitř snapshotů. StateFlow pochází z kotlinx.coroutines.flow a není vázán na Compose. mutableStateOf automaticky spouští rekompozici, StateFlow vyžaduje collectAsState(). Pro UI stav uvnitř Composable je preferován mutableStateOf.
Ano, mutableStateOf lze volat mimo funkci Composable, ale nebude sledován. Pro fungování reaktivity v UI je třeba číst State uvnitř Composable. Mnoho ViewModel používá MutableStateField (obal nad mutableStateOf) pro předávání stavu do UI přes StateFlow.
Snapshot systém zaručuje konzistenci: každá rekompozice vidí konzistentní stav v okamžiku zahájení snapshotu. Změny z různých vláken jsou aplikovány atomicky na hranici snímku, což eliminuje race condition při čtení v rámci jedné kompozice.
Přiřaďte novou hodnotu: count.value = 0 (nebo count = 0 při delegaci). Pokud je potřeba úplné znovuvytvoření State — použijte remember s klíčem: remember(key) { mutableStateOf(initial) } — při změně klíče bude State znovu vytvořen.
Composer používá snapshoty, které seskupují změny: i při stovce přiřazení v jednom snímku se rekompozice provádí pouze jednou. Pro velmi časté aktualizace (animace) použijte Animatable nebo animate*AsState — jsou optimalizovány pro aktualizace snímků.
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é