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, снимак бележи тренутну вредност. Ако током композиције друга промена упише у исти State, снимак види упис, али не дозвољава читање неконзистентних података — читање увек враћа вредност актуелну у тренутку почетка снимка.
При позиву setter-а mutableStateOf.value = newValue, Compose Runtime не покреће одмах рекомпозицију. Уместо тога, промена се региструје у тренутном снимку. Када се снимак примени (на граници оквира), Compose пролази кроз листу измењених State и означава компоненте које читају као Invalid. Тек у следећем оквиру се покреће рекомпозиција. Ово гарантује да UI не буде прецртан десетине пута при каскадним променама.
Глобални и локални снимци: подразумевано mutableStateOf ради у глобалном снимку који се примењује аутоматски. Може се креирати локални снимак путем 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 који ради унутар снимака. StateFlow потиче из kotlinx.coroutines.flow и није везан за Compose. mutableStateOf аутоматски покреће рекомпозицију, StateFlow захтева collectAsState(). За UI стање унутар Composable-а пожељнији је mutableStateOf.
Да, mutableStateOf се може позвати ван Composable функције, али неће бити праћен. За рад реактивности у UI-ју потребно је читати State унутар Composable-а. Многе ViewModel класе користе MutableStateField (омотач око mutableStateOf) за пренос стања у UI кроз StateFlow.
Snapshot систем гарантује конзистентност: свака рекомпозиција види усклађено стање у тренутку почетка снимка. Промене из различитих нити се примењују атомично на граници оквира, што елиминише race condition при читању у оквиру једне композиције.
Доделите нову вредност: count.value = 0 (или count = 0 при делегирању). Ако је потребно потпуно поновно креирање State-а — користите remember са кључем: remember(key) { mutableStateOf(initial) } — при промени кључа State ће бити поново креиран.
Composer користи снимке који групишу промене: чак и са сто додељивања у једном оквиру, рекомпозиција се извршава само једном. За веома честа ажурирања (анимације) користите Animatable или animate*AsState — оптимизовани су за ажурирања по оквирима.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође