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 در مقابل 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید