mutableStateOf: създаване на наблюдаемо състояние и реактивност на Compose

Автор: IT Sectr Публикувано: 2026-06-28 Време за четене: 7 мин

mutableStateOf е функция в Jetpack Compose, която създава контейнер за променливо наблюдаемо състояние. Когато стойността вътре в този контейнер се промени, Compose автоматично стартира рекомпозиция на всички компоненти, които четат това състояние. Без mutableStateOf UI не би могъл да се обновява реактивно при промени на данни. Според Google Android Developers, 2026, mutableStateOf е основният градивен блок за локално състояние в Compose.

Основни точки

  • mutableStateOf създава контейнер MutableState, проследяван от Compose Runtime
  • Рекомпозиция се стартира автоматично при промяна на value на този State обект
  • Делегиране чрез var позволява използване на mutableStateOf без достъп до .value
  • Ключове в remember(mutableStateOf) не са необходими — State сам уведомява Compose за промени
  • Snapshot система гарантира консистентност на четене в многонишкова среда

Какво е mutableStateOf в Jetpack 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

Най-простият начин за деклариране на наблюдаемо състояние е използването на mutableStateOf с remember. Без remember при всяка рекомпозиция би се създавал нов State и всички предишни промени биха се загубили. remember гарантира, че същият MutableState оцелява серия от рекомпозиции, докато Composable функцията остава в състава на композицията.

kotlin
@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.valuecount.value = n
С делегиранеvar count by mutableStateOf(0)countcount = n

Делегирани свойства и var

Механизмът на делегираните свойства на 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 контейнер.

kotlin
    // Персонализирано делегиране за 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 система: как работи mutableStateOf отвътре

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

Нека разгледаме реален сценарий — форма за вход с три полета: имейл, парола и статус на зареждане. И трите полета използват mutableStateOf, но с различни policy и различно влагане. имейл използва делегиране, парола — директен достъп.

kotlin
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 и StateFlow?

mutableStateOf е контейнер, специфичен за Compose, работещ вътре в snapshot. StateFlow идва от kotlinx.coroutines.flow и не е обвързан с Compose. mutableStateOf автоматично стартира рекомпозиция, StateFlow изисква collectAsState(). За UI състояние вътре в Composable, mutableStateOf е за предпочитане.

Може ли mutableStateOf да се използва извън @Composable функции?

Да, mutableStateOf може да бъде извикан извън Composable функция, но няма да бъде проследяван. За работа на реактивността в UI трябва да се чете State вътре в Composable. Много ViewModel използват MutableStateField (обвивка около mutableStateOf) за предаване на състояние към UI чрез StateFlow.

Какво се случва при едновременна промяна на State от две нишки?

Snapshot системата гарантира консистентност: всяка рекомпозиция вижда консистентно състояние към момента на започване на snapshot. Промените от различни нишки се прилагат атомарно на границата на кадъра, което елиминира състезателните условия при четене в рамките на една композиция.

Как да нулираме mutableStateOf до началната стойност?

Присвоете нова стойност: count.value = 0 (или count = 0 при делегиране). Ако е необходимо пълно пресъздаване на State — използвайте remember с ключ: remember(key) { mutableStateOf(initial) } — при промяна на ключа State ще бъде пресъздаден.

Влияе ли mutableStateOf на производителността при чести промени?

Composer използва snapshot, които групират промените: дори при сто присвоявания в един кадър, рекомпозицията се изпълнява само веднъж. За много чести обновявания (анимации) използвайте Animatable или animate*AsState — те са оптимизирани за кадрови обновявания.

Обобщение

  • mutableStateOf създава наблюдаем контейнер MutableState, проследяван от Compose Runtime
  • Делегиране чрез by опростява кода, но не променя механиката на State
  • Snapshot системата гарантира консистентност на четене и предотвратява ненужни рекомпозиции
  • Policy определя кога промяната се счита за значима — structuralEquality, referentialEquality, neverEqual
  • remember е задължителен за запазване на State между рекомпозиции вътре в Composable
  • Препоръка: използвайте mutableStateOf с делегиране за UI състояние и referentialEquality за data класове
  • Избягвайте: създаване на mutableStateOf без remember — всяко присвояване ще създава нов обект

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също