mutableStateOf هي دالة في Jetpack Compose تنشئ حاوية حالة قابلة للتغيير والملاحظة. عندما تتغير القيمة داخل هذه الحاوية، يقوم Compose تلقائياً بتشغيل إعادة التركيب لجميع المكونات التي تقرأ هذه الحالة. بدون mutableStateOf، لم تكن واجهة المستخدم قادرة على التحديث بشكل تفاعلي عند تغير البيانات. وفقاً لـ 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 واحدة، مما يلغي الحالات الوسيطة أثناء التغييرات المتتالية.
المعلمة 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 هو سكر نحوي يبسط الكود لكنه لا يغير الآلية. يترجم مترجم Kotlin var x by mutableStateOf(0) إلى getter/setter يستدعيان mutableStateOf.getValue() و mutableStateOf.setValue(). في bytecode المولد، لا يوجد فرق بين 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، يسجل snapshot القيمة الحالية. إذا كتب تغيير آخر في نفس State أثناء التركيب، يرى snapshot الكتابة لكنه لا يسمح بقراءة بيانات غير متسقة — القراءة دائماً تُرجع القيمة الصالحة في بداية snapshot.
عند استدعاء setter mutableStateOf.value = newValue، لا يقوم Compose Runtime بتشغيل إعادة التركيب فوراً. بدلاً من ذلك، يسجل التغيير في snapshot الحالي. عندما يُطبق snapshot (عند حدود الإطار)، يجتاز Compose قائمة States المتغيرة ويضع علامة على المكونات القارئة كـ Invalid. فقط في الإطار التالي تبدأ إعادة التركيب. هذا يضمن عدم إعادة رسم واجهة المستخدم عشرات المرات أثناء التغييرات المتتالية.
Snapshots عامة ومحلية: افتراضياً، يعمل mutableStateOf في snapshot العام الذي يُطبق تلقائياً. يمكنك إنشاء snapshot محلي عبر Snapshot.takeSnapshot() للقراءة المعزولة دون آثار جانبية. يُستخدم هذا داخل Modifier عندما تحتاج لقراءة State دون الاشتراك في التغييرات. هذا النهج يحسن الأداء ويمنع إعادة التركيب غير المتوقعة.
لنفكر في سيناريو حقيقي — نموذج تسجيل دخول بثلاثة حقول: البريد الإلكتروني، كلمة المرور، وحالة التحميل. جميع الحقول الثلاثة تستخدم mutableStateOf، لكن بسياسات policy مختلفة ومستويات تداخل مختلفة. email يستخدم التفويض، password يستخدم الوصول المباشر.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// حالة واحدة للنموذج، 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 مع فئة بيانات مخصصة LoginState وسياسة referentialEquality. هذا يعني أن إعادة التركيب ستُشغل فقط عند تعيين مثيل جديد من LoginState عبر copy(). isValid يُحسب بناءً على formState ويُعاد حسابه فقط عندما يتغير. هذا النهج يوفر تحكماً واضحاً في إعادة التركيب: كل حقل في النموذج يتغير فقط من خلال إنشاء نسخة جديدة.
الأسئلة الشائعة
mutableStateOf هي حاوية خاصة بـ Compose تعمل داخل snapshots. StateFlow من kotlinx.coroutines.flow وهي غير مرتبطة بـ Compose. mutableStateOf تُشغل إعادة التركيب تلقائياً، بينما StateFlow تتطلب collectAsState(). لحالة واجهة المستخدم داخل Composable، يُفضل mutableStateOf.
نعم، يمكن استدعاء mutableStateOf خارج دوال Composable، لكنه لن يتم تتبعه. للتفاعلية في واجهة المستخدم، يجب قراءة State داخل Composable. العديد من ViewModel تستخدم MutableStateField (غلاف حول mutableStateOf) لتمرير الحالة إلى واجهة المستخدم عبر StateFlow.
نظام Snapshot يضمن الاتساق: كل إعادة تركيب ترى حالة متسقة في بداية snapshot. يتم تطبيق التغييرات من خيوط مختلفة بشكل ذري عند حدود الإطار، مما يلغي حالات السباق أثناء القراءات ضمن تركيب واحد.
قم بتعيين قيمة جديدة: count.value = 0 (أو count = 0 مع التفويض). إذا كنت بحاجة إلى إعادة إنشاء State بالكامل، استخدم remember مع مفتاح: remember(key) { mutableStateOf(initial) } — عندما يتغير المفتاح، سيتم إنشاء State من جديد.
يستخدم Composer snapshots التي تجمع التغييرات: حتى مع مئات التعيينات في إطار واحد، يتم تنفيذ إعادة التركيب مرة واحدة فقط. للتحديثات المتكررة جداً (الرسوم المتحركة)، استخدم Animatable أو animate*AsState — فهي محسنة للتحديثات إطاراً بإطار.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا