mutableStateOf: tworzenie obserwowalnego stanu i reaktywność Compose

Autor: IT Sectr Opublikowano: 2026-06-28 Czas czytania: 7 min

mutableStateOf — to funkcja w Jetpack Compose, która tworzy kontener zmiennego obserwowalnego stanu. Gdy wartość wewnątrz tego kontenera się zmienia, Compose automatycznie uruchamia rekompozycję wszystkich komponentów odczytujących ten stan. Bez mutableStateOf UI nie mógłby reaktywnie aktualizować się przy zmianach danych. Według Google Android Developers, 2026, mutableStateOf jest podstawowym blokiem konstrukcyjnym dla stanu lokalnego w Compose.

Najważniejsze

  • mutableStateOf tworzy kontener MutableState śledzony przez Compose Runtime
  • Rekompozycja uruchamiana jest automatycznie przy zmianie value tego obiektu State
  • Delegacja przez var pozwala używać mutableStateOf bez odwoływania się do .value
  • Klucze w remember(mutableStateOf) nie są potrzebne — State sam powiadamia Compose o zmianach
  • System Snapshot gwarantuje spójność odczytu w środowisku wielowątkowym

Czym jest mutableStateOf w Jetpack Compose

mutableStateOf — to funkcja z pakietu compose.runtime, która tworzy obiekt MutableState<T> przechowujący wartość i mogący powiadamiać Compose Runtime o zmianach. Sygnatura: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Parametr policy określa, kiedy zmiana jest uznawana za znaczącą — przy równości strukturalnej, przy równości referencyjnej lub nigdy.

MutableState — to interfejs z pojedynczą właściwością value: getter do odczytu i setter do zapisu. Przy wywołaniu settera Compose Runtime rejestruje zmianę w snapshocie i oznacza wszystkie funkcje Composable odczytujące tę zmienną State jako wymagające rekompozycji. Proces ten odbywa się synchronicznie w ramach jednego cyklu snapshot, co eliminuje stany pośrednie przy kaskadowych zmianach.

Parameter policy — drugi argument mutableStateOf, określający zachowanie przy porównaniu. structuralEquality() sprawdza equals() — to zachowanie domyślne. referentialEquality() sprawdza === (równość referencyjna). neverEqual() uznaje każde przypisanie za zmianę. Wybór policy wpływa na to, czy rekompozycja zostanie uruchomiona przy przypisaniu „tej samej” wartości.

Składnia i sposoby deklarowania mutableStateOf

Najprostszy sposób zadeklarowania obserwowalnego stanu — użycie mutableStateOf z remember. Bez remember przy każdej rekompozycji tworzony byłby nowy State, a wszystkie poprzednie zmiany zostałyby utracone. remember gwarantuje, że ten sam MutableState przetrwa serię rekompozycji, dopóki funkcja Composable pozostaje w składzie kompozycji.

kotlin
@Composable
fun Counter() {
    // Bez delegacji: odczyt/zapis przez .value
    val count = remember { mutableStateOf(0) }
    Button(onClick = { count.value++ }) {
        Text("Count: ${count.value}")
    }
}

@Composable
fun CounterDelegated() {
    // Z delegacją: var + by = Property Delegation
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

Różnica między dwoma wariantami jest składniowa. Property Delegation (by) wykorzystuje konwencję Kotlin: kompilator generuje wywołania getValue() i setValue() do odczytu i zapisu. Jest to równoważne bezpośredniemu odwołaniu do count.value, ale wygląda jak praca ze zwykłą zmienną. Oba warianty są funkcjonalnie identyczne: Compose śledzi odczyt w getterze i zapis w setterze niezależnie od formy zapisu.

FormaKodOdczytZapis
Bez delegacjival count = mutableStateOf(0)count.valuecount.value = n
Z delegacjąvar count by mutableStateOf(0)countcount = n

Delegowane właściwości i var

Mechanizm delegowanych właściwości Kotlin — to nie cecha Compose, a wbudowana możliwość języka. Każda klasa może zaimplementować operatory getValue(thisRef, property) i setValue(thisRef, property, value), po czym jej instancja może być używana ze słowem kluczowym by. MutableState właśnie tak działa: getValue zwraca bieżącą wartość, a setValue przypisuje nową.

Ważna różnica: val i var. mutableStateOf można przypisać zarówno do val, jak i var. W przypadku val (val count = mutableStateOf(0)) sam obiekt MutableState jest niezmienny, ale jego właściwość value może być zmieniana. W przypadku var (var count by mutableStateOf(0)) delegacja daje złudzenie pracy z prymitywem, ale w rzeczywistości setter wywołuje setValue na MutableState. Wybór między val a var to wybór między jawnym a niejawnym dostępem do .value.

State Delegation — to syntaktyczny cukier, który upraszcza kod, ale nie zmienia mechaniki. Kompilator Kotlin transluje var x by mutableStateOf(0) na getter/setter, które wywołują mutableStateOf.getValue() i mutableStateOf.setValue(). W wygenerowanym bajtkodzie nie ma różnicy między val a var z by — oba działają przez ten sam kontener MutableState.

kotlin
    // Niestandardowa delegacja dla stanu Compose
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("") }
}

System Snapshot: jak działa mutableStateOf wewnątrz

Snapshot — to mechanizm Compose Runtime zapewniający spójność odczytu State przy równoległych zmianach. Gdy funkcja Composable odczytuje mutableStateOf, migawka rejestruje bieżącą wartość. Jeśli podczas kompozycji inna zmiana zapisuje do tego samego State, migawka widzi zapis, ale nie pozwala na odczyt niespójnych danych — odczyt zawsze zwraca wartość aktualną na moment rozpoczęcia migawki.

Przy wywołaniu settera mutableStateOf.value = newValue Compose Runtime nie uruchamia natychmiast rekompozycji. Zamiast tego zmiana jest rejestrowana w bieżącym snapshocie. Gdy snapshot jest stosowany (na granicy ramki), Compose przechodzi przez listę zmienionych State i oznacza czytające komponenty jako Invalid. Dopiero w następnej ramce uruchamiana jest rekompozycja. To gwarantuje, że UI nie jest przerysowywany dziesiątki razy przy kaskadowych zmianach.

Globalne i lokalne snapshoty: domyślnie mutableStateOf działa w globalnym snapshocie, który jest stosowany automatycznie. Można utworzyć lokalny snapshot przez Snapshot.takeSnapshot() dla izolowanego odczytu bez efektów ubocznych. Jest to używane wewnątrz Modifier, gdzie trzeba odczytać State, ale nie subskrybować się na zmiany. Takie podejście optymalizuje wydajność i zapobiega nieoczekiwanym rekompozycjom.

Przykłady użycia mutableStateOf

Rozważmy rzeczywisty scenariusz — formularz logowania z trzema polami: email, hasło i status ładowania. Wszystkie trzy pola używają mutableStateOf, ale z różnymi policy i różnym zagnieżdżeniem. email używa delegacji, hasło — bezpośredniego dostępu.

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) {
    // Pojedynczy stan dla formularza, polityka = 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("Email") }
        )
        OutlinedTextField(
            value = formState.password,
            onValueChange = { formState = formState.copy(password = it) },
            label = { Text("Hasło") },
            visualTransformation = PasswordVisualTransformation()
        )
        Button(
            onClick = { onLogin(formState.email, formState.password) },
            enabled = isValid
        ) {
            Text("Zaloguj się")
        }
    }
}

W tym przykładzie mutableStateOf jest używane z niestandardową klasą danych LoginState i policy referentialEquality. Oznacza to, że rekompozycja zostanie uruchomiona tylko przy przypisaniu nowej instancji LoginState przez copy(). isValid jest obliczane na podstawie formState i przeliczane tylko przy jego zmianie. Takie podejście daje wyraźną kontrolę nad rekompozycjami: każde pole formularza zmienia się tylko przez utworzenie nowej kopii.

Często zadawane pytania

Czym różni się mutableStateOf od StateFlow?

mutableStateOf — to kontener specyficzny dla Compose, działający wewnątrz snapshotów. StateFlow pochodzi z kotlinx.coroutines.flow i nie jest związany z Compose. mutableStateOf automatycznie uruchamia rekompozycję, StateFlow wymaga collectAsState(). Dla stanu UI wewnątrz Composable preferowany jest mutableStateOf.

Czy można używać mutableStateOf poza funkcjami @Composable?

Tak, mutableStateOf można wywoływać poza funkcją Composable, ale nie będzie śledzony. Do działania reaktywności w UI należy czytać State wewnątrz Composable. Wiele ViewModel używa MutableStateField (opakowania na mutableStateOf) do przekazywania stanu do UI przez StateFlow.

Co się stanie przy jednoczesnej zmianie State z dwóch wątków?

Snapshot system gwarantuje spójność: każda rekompozycja widzi spójny stan na moment rozpoczęcia migawki. Zmiany z różnych wątków są stosowane atomowo na granicy ramki, co eliminuje race condition przy odczycie w ramach jednej kompozycji.

Jak zresetować mutableStateOf do wartości początkowej?

Przypisz nową wartość: count.value = 0 (lub count = 0 przy delegacji). Jeśli potrzebne jest całkowite odtworzenie State — użyj remember z kluczem: remember(key) { mutableStateOf(initial) } — przy zmianie key State zostanie utworzony od nowa.

Czy mutableStateOf wpływa na wydajność przy częstych zmianach?

Composer używa snapshotów, które grupują zmiany: nawet przy stu przypisaniach w jednej ramce rekompozycja wykonywana jest tylko raz. Dla bardzo częstych aktualizacji (animacje) użyj Animatable lub animate*AsState — są zoptymalizowane do odświeżania klatek.

Podsumowanie

  • mutableStateOf tworzy obserwowalny kontener MutableState śledzony przez Compose Runtime
  • Delegacja przez by upraszcza kod, ale nie zmienia mechaniki działania State
  • Snapshot system gwarantuje spójność odczytu i zapobiega zbędnym rekompozycjom
  • Policy określa, kiedy zmiana jest uznawana za znaczącą — structuralEquality, referentialEquality, neverEqual
  • remember jest obowiązkowy do zachowania State między rekompozycjami wewnątrz Composable
  • Zalecenie: używaj mutableStateOf z delegacją dla stanu UI i referentialEquality dla klas danych
  • Unikaj: tworzenia mutableStateOf bez remember — każde przypisanie będzie tworzyć nowy obiekt

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również