mutableStateOf е функция в Jetpack Compose, която създава контейнер за променливо наблюдаемо състояние. Когато стойността вътре в този контейнер се промени, Compose автоматично стартира рекомпозиция на всички компоненти, които четат това състояние. Без mutableStateOf UI не би могъл да се обновява реактивно при промени на данни. Според Google Android Developers, 2026, mutableStateOf е основният градивен блок за локално състояние в Compose.
Основни точки
mutableStateOf е функция от пакета compose.runtime, която създава обект MutableState<T>, съхраняващ стойност и способен да уведомява Compose Runtime за промени. Сигнатура: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Параметърът policy определя кога промяната се счита за значима — при структурно равенство, при референтно равенство или никога.
MutableState е интерфейс с едно единствено свойство value: getter за четене и setter за писане. При извикване на setter, Compose Runtime записва промяната в snapshot и маркира всички Composable функции, които четат тази State променлива, като изискващи рекомпозиция. Този процес се случва синхронно в рамките на един snapshot цикъл, което елиминира междинните състояния при каскадни промени.
Parameter policy — вторият аргумент на mutableStateOf, определящ поведението при сравнение. structuralEquality() проверява equals() — това е поведението по подразбиране. referentialEquality() проверява === (референтно равенство). neverEqual() счита всяко присвояване за промяна. Изборът на policy влияе върху това дали рекомпозицията ще се стартира при присвояване на същата стойност.
Най-простият начин за деклариране на наблюдаемо състояние е използването на mutableStateOf с remember. Без remember при всяка рекомпозиция би се създавал нов State и всички предишни промени биха се загубили. remember гарантира, че същият MutableState оцелява серия от рекомпозиции, докато Composable функцията остава в състава на композицията.
@Composable
fun Counter() {
// Без делегиране: четене/писане чрез .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// С делегиране: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
Разликата между двата варианта е синтактична. Property Delegation (by) използва конвенцията на Kotlin: компилаторът генерира извиквания на getValue() и setValue() за четене и писане. Това е еквивалентно на директен достъп до count.value, но изглежда като работа с обикновена променлива. И двата варианта са функционално идентични: Compose проследява четенето в getter и писането в setter независимо от формата на запис.
| Форма | Код | Четене | Писане |
|---|---|---|---|
| Без делегиране | val count = mutableStateOf(0) | count.value | count.value = n |
| С делегиране | var count by mutableStateOf(0) | count | count = n |
Механизмът на делегираните свойства на Kotlin не е характеристика на Compose, а вградена възможност на езика. Всяка класа може да имплементира операторите getValue(thisRef, property) и setValue(thisRef, property, value), след което нейният инстанция може да се използва с ключовата дума by. MutableState работи точно по този начин: getValue връща текущата стойност, а setValue присвоява нова.
Важна разлика: val и var. mutableStateOf може да бъде присвоен както на val, така и на var. В случая на val (val count = mutableStateOf(0)) самият MutableState обект е неизменим, но неговото свойство value може да бъде променяно. В случая на var (var count by mutableStateOf(0)) делегирането създава илюзия за работа с примитив, но всъщност setter извиква setValue на MutableState. Изборът между val и var е избор между явен и неявен достъп до .value.
State Delegation е синтактична захар, която опростява кода, но не променя механиката. Kotlin компилаторът транслира var x by mutableStateOf(0) в getter/setter, които извикват mutableStateOf.getValue() и mutableStateOf.setValue(). В генерирания байткод няма разлика между val и var с by — и двете работят чрез един и същ MutableState контейнер.
// Персонализирано делегиране за 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 е механизъм на Compose Runtime, който осигурява консистентност на четене на State при паралелни промени. Когато Composable функция чете mutableStateOf, snapshot записва текущата стойност. Ако по време на композицията друга промяна запише в същия State, snapshot вижда записа, но не позволява четене на неконсистентни данни — четенето винаги връща стойността, актуална към момента на започване на snapshot.
При извикване на setter mutableStateOf.value = newValue, Compose Runtime не стартира веднага рекомпозиция. Вместо това промяната се регистрира в текущия snapshot. Когато snapshot се приложи (на границата на кадъра), Compose преминава през списъка с променени State и маркира четещите компоненти като Invalid. Едва в следващия кадър се стартира рекомпозиция. Това гарантира, че UI не се прерисува десетки пъти при каскадни промени.
Глобални и локални snapshot: по подразбиране mutableStateOf работи в глобален snapshot, който се прилага автоматично. Може да се създаде локален snapshot чрез Snapshot.takeSnapshot() за изолирано четене без странични ефекти. Това се използва вътре в Modifier, където трябва да се прочете State, но не е необходимо да се абонирате за промени. Този подход оптимизира производителността и предотвратява неочаквани рекомпозиции.
Нека разгледаме реален сценарий — форма за вход с три полета: имейл, парола и статус на зареждане. И трите полета използват mutableStateOf, но с различни policy и различно влагане. имейл използва делегиране, парола — директен достъп.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// Единствен State за форма, 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("Имейл") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("Парола") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Вход")
}
}
}
В този пример mutableStateOf се използва с персонализиран data клас LoginState и policy referentialEquality. Това означава, че рекомпозицията ще се стартира само при присвояване на нова инстанция на LoginState чрез copy(). isValid се изчислява на базата на formState и се преизчислява само при неговата промяна. Този подход дава ясен контрол върху рекомпозициите: всяко поле на формата се променя само чрез създаване на ново копие.
Често задавани въпроси
mutableStateOf е контейнер, специфичен за Compose, работещ вътре в snapshot. StateFlow идва от kotlinx.coroutines.flow и не е обвързан с Compose. mutableStateOf автоматично стартира рекомпозиция, StateFlow изисква collectAsState(). За UI състояние вътре в Composable, mutableStateOf е за предпочитане.
Да, mutableStateOf може да бъде извикан извън Composable функция, но няма да бъде проследяван. За работа на реактивността в UI трябва да се чете State вътре в Composable. Много ViewModel използват MutableStateField (обвивка около mutableStateOf) за предаване на състояние към UI чрез StateFlow.
Snapshot системата гарантира консистентност: всяка рекомпозиция вижда консистентно състояние към момента на започване на snapshot. Промените от различни нишки се прилагат атомарно на границата на кадъра, което елиминира състезателните условия при четене в рамките на една композиция.
Присвоете нова стойност: count.value = 0 (или count = 0 при делегиране). Ако е необходимо пълно пресъздаване на State — използвайте remember с ключ: remember(key) { mutableStateOf(initial) } — при промяна на ключа State ще бъде пресъздаден.
Composer използва snapshot, които групират промените: дори при сто присвоявания в един кадър, рекомпозицията се изпълнява само веднъж. За много чести обновявания (анимации) използвайте Animatable или animate*AsState — те са оптимизирани за кадрови обновявания.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също