MutableState — είναι μια διεπαφή στο Jetpack Compose που αντιπροσωπεύει ένα δοχείο για μια μεταβλητή παρατηρήσιμη τιμή. Αποτελεί τη βάση του αντιδραστικού συστήματος Compose: κάθε φορά που η τιμή του MutableState αλλάζει μέσω setter, το Compose Runtime ειδοποιεί όλα τα διαβάζοντα στοιχεία και εκκινεί την ανασύνθεση (recomposition). Σύμφωνα με το 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 value καλείται, το τρέχον snapshot καταγράφει την εγγραφή και σημειώνει όλα τα καταχωρημένα 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 — είναι υλοποιήσεις τροποποιήσιμων λιστών και χαρτών, συμβατές με snapshot. Επιτρέπουν την παρακολούθηση όχι μόνο της αντικατάστασης τιμής, αλλά και των εσωτερικών αλλαγών: προσθήκη στοιχείου στη λίστα, διαγραφή, τροποποίηση υπάρχοντος στοιχείου. Για τέτοιες δομές, οι mutableStateListOf() και mutableStateMapOf() δημιουργούν αντίστοιχες παρατηρήσιμες συλλογές.
| Διεπαφή | Σκοπός | Μέθοδος δημιουργίας |
|---|---|---|
| State<T> | Δοχείο μόνο για ανάγνωση | — |
| MutableState<T> | Δοχείο ανάγνωσης και εγγραφής | mutableStateOf() |
| MutableIntState | Πρωτόγονος Int χωρίς boxing | mutableIntStateOf() |
| MutableFloatState | Πρωτόγονος Float χωρίς boxing | mutableFloatStateOf() |
| SnapshotStateList | Παρατηρήσιμη λίστα | mutableStateListOf() |
| SnapshotStateMap | Παρατηρήσιμος χάρτης | mutableStateMapOf() |
SnapshotMutationPolicy — είναι μια διεπαφή που καθορίζει πότε μια αλλαγή του MutableState θεωρείται σημαντική. Το mutableStateOf δέχεται policy ως δεύτερο όρισμα. Τυπικές υλοποιήσεις: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (πάντα θεωρεί την αλλαγή σημαντική). Για προσαρμοσμένη λογική μπορείτε να υλοποιήσετε δικό σας policy.
structuralEquality() — προεπιλεγμένη συμπεριφορά. Το Compose συγκρίνει τη νέα τιμή με την παλιά μέσω equals(). Εάν το αποτέλεσμα είναι true — η ανασύνθεση ΔΕΝ εκκινείται. Αυτό είναι βολικό για πρωτόγονους τύπους και data class, όπου δύο στιγμιότυπα με τα ίδια πεδία θεωρούνται ίσα. Πρόβλημα: εάν το data class περιέχει List, η equals() εκτελεί βαθιά σύγκριση, η οποία μπορεί να είναι δαπανηρή για μεγάλες λίστες.
referentialEquality() — συγκρίνει αναφορές μέσω ===. Η ανασύνθεση εκκινείται μόνο κατά την ανάθεση άλλου αντικειμένου, ακόμα κι αν το περιεχόμενο είναι ταυτόσημο. Αυτό είναι βέλτιστο για αμετάβλητα data class, όπου κάθε νέο στιγμιότυπο εγγυημένα σημαίνει αλλαγή. 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, το οποίο σε κάθε εγγραφή δημιουργεί ένα αντικείμενο στο heap. Το MutableIntState αποθηκεύει int (πρωτόγονο), εξαλείφοντας πλήρως την επιβάρυνση συσκευασίας. Αυτό είναι ιδιαίτερα σημαντικό σε ενημερώσεις υψηλής συχνότητας — μετρητές, θέσεις κύλισης, τιμές κινούμενων σχεδίων.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — συναρτήσεις που δημιουργούν πρωτόγονες MutableState. Οι διεπαφές ονομάζονται MutableIntState, MutableFloatState, MutableLongState. Επεκτείνουν τις MutableState<Int>, MutableState<Float> και MutableState<Long> αντίστοιχα, προσθέτοντας την ιδιότητα intValue ως γρήγορη πρόσβαση στον πρωτόγονο. Στην εσωτερική τους υλοποίηση χρησιμοποιείται AtomicInteger για ανάγνωση/εγγραφή χωρίς κλείδωμα.
Εφαρμογή: μετρητές (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 για δυναμική λίστα εργασιών. Και οι δύο χρησιμοποιούν ανάθεση (delegation) για συντομία κώδικα.
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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης