MutableState — наблюдаемо състояние и механизъм за актуализация в Compose

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

MutableState — е интерфейс в Jetpack Compose, който представлява контейнер за променяема наблюдаема стойност. Той е основата на реактивната система на Compose: всеки път, когато стойността на MutableState се променя чрез setter, Compose Runtime уведомява всички четящи компоненти и стартира рекомпозиция. Според Google Android Developers, 2026, разбирането на MutableState е задължително за коректна работа със състояние в декларативния UI.

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

  • MutableState — интерфейс на compose.runtime с едно свойство value (getter + setter)
  • State — родителски интерфейс само за четене, MutableState добавя възможност за запис
  • Рекомпозиция се стартира при извикване на setter на value в рамките на активен snapshot цикъл
  • SnapshotMutationPolicy определя кога промяната се счита за значима за рекомпозиция
  • MutableIntState и аналогични — оптимизирани примитивни версии на MutableState

Какво е MutableState в Jetpack Compose

MutableState — е интерфейс от пакета androidx.compose.runtime, който декларира едно свойство: override var value: T. Getter връща текущата стойност, setter записва нова и уведомява Compose Runtime за промяната. Интерфейсът наследява от State<T>, при който value е достъпен само за четене. Такава двустепенна архитектура позволява разделяне на достъпа: компонентът, който трябва само да чете стойността, получава State<T>, а компонентът-собственик — MutableState<T>.

Имплементацията по подразбиране на MutableState е вътрешният клас SnapshotMutableStateImpl, който използва механизма на snapshot за проследяване на промени. Когато setter на value бъде извикан, текущият snapshot записва записа и маркира всички регистрирани ObservedScope (области на наблюдение) като невалидни. Тези области (обикновено Composable функции) ще бъдат рекомпозирани в следващия кадър. Целият процес се случва синхронно и без блокиране благодарение на архитектурата Lock-free snapshot.

State срещу MutableState: State — е интерфейс само за четене, използван за публичните API на компонентите. Когато декларирате параметър на Composable функция като State<Int>, гарантирате, че компонентът може да чете, но не и да променя състоянието. MutableState се използва вътре в компонента-собственик. Такова разделение — една от основните практики на Compose — предотвратява неоторизирани промени.

Йерархия на State, MutableState и производни интерфейси

Йерархията на интерфейсите за състояние в Compose има няколко нива. На върха — State<T> с value само за четене. По-долу — MutableState<T> с value за четене и запис. Следват специализирани примитивни версии: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState и други, които избягват автоматичното опаковане (boxing) на примитиви.

MutableDoubleState и MutableLongState — по-рядко срещани, но съществуващи типове. Интерфейси за колекции: MutableListState — за проследяване на промени вътре в списък, MutableStateMap — за карти. Всеки от тези интерфейси е оптимизиран за конкретен сценарий и разширява базовия MutableState с допълнителни методи за работа с колекция.

SnapshotStateList и SnapshotStateMap — са имплементации на променяеми списъци и карти, съвместими със snapshot. Те позволяват проследяване не само на замяна на стойност, но и на вътрешни промени: добавяне на елемент в списък, изтриване, промяна на съществуващ елемент. За такива структури mutableStateListOf() и mutableStateMapOf() създават съответните наблюдаеми колекции.

ИнтерфейсПредназначениеМетод за създаване
State<T>Контейнер само за четене
MutableState<T>Контейнер за четене и записmutableStateOf()
MutableIntStateПримитивен Int без boxingmutableIntStateOf()
MutableFloatStateПримитивен Float без boxingmutableFloatStateOf()
SnapshotStateListНаблюдаем списъкmutableStateListOf()
SnapshotStateMapНаблюдаема картаmutableStateMapOf()

SnapshotMutationPolicy: кога да стартираме рекомпозиция

SnapshotMutationPolicy — е интерфейс, който определя кога промяната на MutableState се счита за значима. mutableStateOf приема policy като втори аргумент. Стандартни имплементации: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (винаги счита промяната за значима). За собствена логика можете да имплементирате свой policy.

structuralEquality() — поведение по подразбиране. Compose сравнява новата стойност със старата чрез equals(). Ако резултатът е true — рекомпозиция НЕ се стартира. Това е удобно за примитиви и data класове, където два инстанции с еднакви полета се считат за равни. Проблем: ако data клас съдържа List, equals() извършва дълбоко сравнение, което може да бъде скъпо за големи списъци.

referentialEquality() — сравнява референции чрез ===. Рекомпозиция се стартира само при присвояване на друг обект, дори ако съдържанието е идентично. Това е оптимално за неизменяеми data класове, където всеки нов инстанс гарантирано означава промяна. neverEqualPolicy() — винаги счита промяната за значима, без да извършва сравнение. Полезно, когато setter се извиква рядко и не е необходимо да се губи време за equals.

kotlin
    // Сравнение на политики на практика
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: рекомпозиция САМО ако данните са се променили
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: рекомпозиция при ВСЯКО присвояване
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() със същите полета НЕ задейства рекомпозиция
    // user2: дори user2.copy() == user2 задейства рекомпозиция (нова референция)
}

Примитивни State: MutableIntState, MutableFloatState, MutableLongState

Примитивните MutableIntState и аналогични — са специализирани интерфейси, които съхраняват примитиви без автоматично опаковане (boxing). Обикновеният MutableState<Int> съхранява Int като Integer, което при всеки запис създава обект в heap. MutableIntState съхранява int (примитив), напълно елиминирайки overhead-а на опаковане. Това е особено важно при високочестотни актуализации — броячи, позиции на скрол, анимационни стойности.

mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — функции, които създават примитивни MutableState. Интерфейсите се наричат MutableIntState, MutableFloatState, MutableLongState. Те разширяват съответно MutableState<Int>, MutableState<Float> и MutableState<Long>, добавяйки свойството intValue за бърз достъп до примитива. В тяхната вътрешна имплементация се използва AtomicInteger за lock-free четене/запис.

Приложение: броячи (Int), позиции на скрол (Float offset), времеви отпечатъци (Long). В повечето ежедневни сценарии разликата в производителността е незабележима, но в LazyList с хиляди елементи и анимация на преходи примитивните State дават осезаемо подобрение. Google препоръчва използването на примитивни State за типични сценарии вместо универсалния mutableStateOf.

kotlin
@Composable
fun ScrollCounter() {
    // Лошо: опаковане при всяка актуализация
    var badCount by remember { mutableStateOf(0) }

    // Добре: без опаковане, примитивно съхранение
    var goodCount by remember { mutableIntStateOf(0) }

    // Използването е идентично
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

Практически примери за работа с MutableState

Нека разгледаме компонента TodoList, където MutableState се използва в две форми: като отделни променливи за състояние на вход и като SnapshotStateList за динамичен списък със задачи. И двете използват делегиране за краткост на кода.

kotlin
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("Добави") }
        }

        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 създава SnapshotStateList — променяем списък, който проследява промените на отделни елементи. При извикване на items.add() и items[n] = newValue Compose вижда мутацията и рекомпозира само тези елементи на LazyColumn, които са се променили. inputText — обикновен MutableState<String>. Комбинацията от два типа MutableState (единичен и колекция) — типичен модел за екрани с форми и списъци.

Често задавани въпроси

Може ли MutableState да се използва без remember?

MutableState без remember ще бъде създаван наново при всяка рекомпозиция. Всяко ново извикване на mutableStateOf създава нов обект, а старата стойност се губи. Винаги използвайте remember за запазване на State между рекомпозиции, освен ако State не се създава извън Composable (например в ViewModel).

Как да преобразуваме MutableState в обикновена променлива?

Прочетете .value веднъж извън snapshot чрез snapshot { }. Но това изключва реактивността — промените вече няма да предизвикват рекомпозиция. За еднократно четене без абонамент използвайте currentValue() вътре в snapshot без четене.

Кое е по-бързо: mutableStateOf или mutableIntStateOf?

mutableIntStateOf е по-бързо, тъй като не изисква опаковане на int в Integer. При хиляди актуализации в секунда (анимация, скрол) разликата може да достигне 30-50% от времето за алокация. При редки актуализации (кликвания, въвеждане на текст) разликата е незначителна.

Може ли MutableState да се използва като аргумент на Composable функция?

Може, но не се препоръчва. Вместо MutableState предавайте State (само за четене) + ламбда onValueChange. Това имплементира модела State Hoisting и прави компонента преизползваем. Компонентите, които приемат MutableState, нарушават еднопосочния поток от данни.

Как да създадем персонализирана имплементация на MutableState?

Имплементирайте интерфейса MutableState и предоставете override var value с getter и setter. В setter можете да добавите валидация или логване. За обратна съвместимост с Compose Runtime увийте имплементацията си в snapshotFlow или използвайте snapshotIncrement.

Обобщение

  • MutableState — основен интерфейс за променяемо наблюдаемо състояние в Compose
  • State — версия само за четене за предаване на данни без право на промяна
  • SnapshotMutationPolicy управлява условията за стартиране на рекомпозиция при промяна
  • Примитивни State (MutableIntState и др.) елиминират overhead-а на автоматично опаковане
  • SnapshotStateList и SnapshotStateMap проследяват вътрешни промени на колекции
  • Рекомпозиция се стартира автоматично при всяко извикване на setter на value
  • Препоръка: използвайте MutableState за притежаване на състояние и State за предаване надолу

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

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

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

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