MutableState — је интерфејс у Jetpack Compose-у који представља контејнер за променљиву посматрану вредност. Он је основа реактивног система Compose-а: сваки пут када се вредност MutableState-а промени преко setter-а, Compose Runtime обавештава све компоненте које читају и покреће рекомпозицију. Према Google Android Developers, 2026, разумевање MutableState-а је обавезно за правилан рад са стањем у декларативном UI-ју.
Главно
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-а, која спречава неовлашћене измене.
Хијерархија интерфејса стања у 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 — је интерфејс који одређује када се промена MutableState-а сматра значајном. mutableStateOf прихвата policy као други аргумент. Стандардне имплементације: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (увек сматра промену значајном). За сопствену логику можете имплементирати свој policy.
structuralEquality() — подразумевано понашање. Compose упоређује нову вредност са старом преко equals(). Ако је резултат true — рекомпозиција се НЕ покреће. Ово је згодно за примитиве и data класе, где се две инстанце са истим пољима сматрају једнаким. Проблем: ако data класа садржи List, equals() врши дубинско поређење, што може бити скупо за велике листе.
referentialEquality() — пореди референце преко ===. Рекомпозиција се покреће само при додели другог објекта, чак и ако је садржај идентичан. Ово је оптимално за непроменљиве data класе, где свака нова инстанца гарантовано значи промену. neverEqualPolicy() — увек сматра промену значајном, без вршења поређења. Корисно када се setter ретко позива и не треба трошити време на equals.
// Поређење политика у пракси
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 покреће рекомпозицију (нова референца)
}
Примитивни 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.
@Composable
fun ScrollCounter() {
// Лоше: паковање при сваком ажурирању
var badCount by remember { mutableStateOf(0) }
// Добро: без паковања, примитивно складиштење
var goodCount by remember { mutableIntStateOf(0) }
// Коришћење је идентично
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Размотримо компоненту TodoList, где се MutableState користи у два облика: као одвојене променљиве за стање уноса и као SnapshotStateList за динамичку листу задатака. Оба користе делегирање за краткоћу кода.
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-а ће се поново креирати при свакој рекомпозицији. Сваки нови позив mutableStateOf ствара нови објекат, а стара вредност се губи. Увек користите remember за чување State-а између рекомпозиција, осим ако се State не креира ван Composable-а (нпр. у ViewModel-у).
Прочитајте .value једном ван snapshot-а преко snapshot { }. Али ово искључује реактивност — промене више неће изазивати рекомпозицију. За једнократно читање без претплате користите currentValue() унутар snapshot-а без читања.
mutableIntStateOf је брже јер не захтева паковање int у Integer. При хиљадама ажурирања у секунди (анимација, скроловање) разлика може достићи 30-50% времена алокације. При ретким ажурирањима (кликови, унос текста) разлика је занемарљива.
Може, али се не препоручује. Уместо MutableState-а, проследите State (само за читање) + ламбду onValueChange. Ово имплементира образац State Hoisting и чини компоненту поново употребљивом. Компоненте које прихватају MutableState нарушавају једносмерни ток података.
Имплементирајте интерфејс MutableState и обезбедите override var value са getter-ом и setter-ом. У setter-у можете додати валидацију или логирање. За уназадну компатибилност са Compose Runtime-ом, умотајте своју имплементацију у snapshotFlow или користите snapshotIncrement.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође