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 вредности позове, тренутни снимак бележи упис и означава све регистроване ObservedScope-ове (области посматрања) као неважеће. Ове области (обично Composable функције) биће рекомпоноване у следећем кадру. Цео процес се одвија синхроно и без блокирања захваљујући Lock-free snapshot архитектури.

State vs 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 — су имплементације променљивих листи и мапи, компатибилне са снимцима. Оне омогућавају праћење не само замене вредности, већ и унутрашњих промена: додавање елемента у листу, брисање, измену постојећег елемента. За такве структуре mutableStateListOf() и mutableStateMapOf() креирају одговарајуће посматране колекције.

ИнтерфејсНаменаМетод креирања
State<T>Контејнер само за читање
MutableState<T>Контејнер за читање и писањеmutableStateOf()
MutableIntStateПримитивни Int без box-овањаmutableIntStateOf()
MutableFloatStateПримитивни Float без box-овањаmutableFloatStateOf()
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, што при сваком упису ствара објекат на хеап-у. 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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође