الحالة في التطبيق هي أي قيمة يمكن أن تتغير بمرور الوقت. وهذا تعريف واسع جدًا يشمل كل شيء، بدءًا من قاعدة بيانات Room إلى متغير في فئة.
تعرض جميع تطبيقات Android الحالة للمستخدم. في ما يلي بعض الأمثلة على الحالة في تطبيقات Android:
- شريط معلومات يظهر عند تعذُّر إنشاء اتصال بالشبكة
- مشاركة مدونة والتعليقات المرتبطة بها
- رسوم متحركة على شكل تموّجات على الأزرار يتم تشغيلها عندما ينقر المستخدم عليها
- ملصقات يمكن للمستخدم رسمها فوق صورة
تساعدك Jetpack Compose في تحديد مكان وكيفية تخزين الحالة واستخدامها في تطبيق Android بشكل واضح. يركّز هذا الدليل على العلاقة بين الحالة والعناصر القابلة للإنشاء، وعلى واجهات برمجة التطبيقات التي توفّرها Jetpack Compose للتعامل مع الحالة بسهولة أكبر.
الحالة والتركيب
تتسم Compose بأسلوبها التصريحي، وبالتالي فإنّ الطريقة الوحيدة لتعديلها هي استدعاء العنصر القابل للإنشاء نفسه باستخدام وسيطات جديدة. وتمثّل هذه الوسيطات حالة واجهة المستخدم. في كل مرة يتم فيها تعديل حالة، تحدث إعادة إنشاء. نتيجةً لذلك، لا يتم تعديل عناصر مثل TextField
تلقائيًا كما يحدث في طرق العرض المستندة إلى XML الإجرائي. يجب إخبار العنصر القابل للإنشاء بشكل صريح بالحالة الجديدة
لكي يتم تعديله وفقًا لذلك.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
إذا شغّلت هذا الرمز وحاولت إدخال نص، ستلاحظ أنّه لا يحدث شيء. ويرجع ذلك إلى أنّ TextField
لا يتم تعديله تلقائيًا، بل يتم تعديله عند تغيير المَعلمة value
. ويرجع ذلك إلى طريقة عمل الإنشاء وإعادة الإنشاء في Compose.
لمزيد من المعلومات حول الإنشاء الأوّلي وإعادة الإنشاء، راجِع التفكير في Compose.
الحالة في العناصر القابلة للإنشاء
يمكن أن تستخدم الدوال القابلة للإنشاء واجهة برمجة التطبيقات
remember
لتخزين عنصر في الذاكرة. يتم تخزين القيمة التي يتم احتسابها بواسطة remember
في Composition أثناء عملية الإنشاء الأولية، ويتم عرض القيمة المخزَّنة أثناء عملية إعادة الإنشاء.
يمكن استخدام remember
لتخزين الكائنات القابلة للتغيير وغير القابلة للتغيير.
تنشئ mutableStateOf
كائنًا قابلاً للمراقبة
MutableState<T>
،
وهو نوع كائن قابل للمراقبة مدمج مع وقت تشغيل Compose.
interface MutableState<T> : State<T> {
override var value: T
}
سيؤدي إجراء أي تغييرات على value
إلى إعادة إنشاء أي دوال قابلة للإنشاء تقرأ value
.
هناك ثلاث طرق لتعريف عنصر MutableState
في عنصر قابل للإنشاء:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
هذه التعريفات متكافئة، ويتم توفيرها كبنية مختصرة للاستخدامات المختلفة للحالة. عليك اختيار الرمز الذي يسهل قراءته في الدالة البرمجية القابلة للإنشاء التي تكتبها.
يتطلّب بناء الجملة الخاص بالدالة by
عمليات الاستيراد التالية:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
يمكنك استخدام القيمة التي تم تذكُّرها كمعلَمة لعناصر أخرى قابلة للإنشاء أو حتى كمنطق في عبارات لتغيير العناصر القابلة للإنشاء التي يتم عرضها. على سبيل المثال، إذا كنت لا تريد عرض رسالة الترحيب إذا كان الاسم فارغًا، استخدِم الحالة في عبارة if
:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
على الرغم من أنّ remember
تساعدك في الاحتفاظ بالحالة أثناء عمليات إعادة التركيب، لا يتم الاحتفاظ بالحالة عند حدوث تغييرات في الإعدادات. لإجراء ذلك، يجب استخدام
rememberSaveable
. تحفظ rememberSaveable
تلقائيًا أي قيمة يمكن حفظها في Bundle
. بالنسبة إلى القيم الأخرى، يمكنك إدخال عنصر حفظ مخصّص.
أنواع الحالات الأخرى المتوافقة
لا تتطلّب Compose استخدام MutableState<T>
للاحتفاظ بالحالة، بل تتوافق مع أنواع أخرى قابلة للملاحظة. قبل قراءة نوع قابل للمراقبة آخر في Compose، عليك تحويله إلى State<T>
حتى تتم إعادة إنشاء العناصر القابلة للإنشاء تلقائيًا عند تغيُّر الحالة.
تتضمّن Compose دوال لإنشاء State<T>
من أنواع العناصر القابلة للملاحظة الشائعة الاستخدام في تطبيقات Android. قبل استخدام عمليات الدمج هذه، أضِف العناصر المناسبة كما هو موضّح أدناه:
Flow
:collectAsStateWithLifecycle()
تجمع
collectAsStateWithLifecycle()
القيم منFlow
بطريقة تراعي مراحل النشاط، ما يتيح لتطبيقك الحفاظ على موارد التطبيق. تمثّل هذه السمة آخر قيمة تم إصدارها من ComposeState
. استخدِم واجهة برمجة التطبيقات هذه كطريقة مقترَحة لجمع بيانات مسارات الإحالة الناجحة على تطبيقات Android.يجب توفُّر التبعيات التالية في ملف
build.gradle
(يجب أن يكون الإصدار 2.6.0-beta01 أو إصدارًا أحدث):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
-
تشبه الدالة
collectAsState
الدالةcollectAsStateWithLifecycle
، لأنّها تجمع أيضًا القيم منFlow
وتحوّلها إلىState
في Compose.استخدِم
collectAsState
للرمز البرمجي المتوافق مع جميع الأنظمة الأساسية بدلاً منcollectAsStateWithLifecycle
الذي يتوافق مع Android فقط.لا تتطلّب
collectAsState
أي تبعيات إضافية لأنّها متاحة فيcompose-runtime
. -
يبدأ
observeAsState()
في مراقبةLiveData
ويعرض قيمه من خلالState
.يجب توفُّر التبعية التالية في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.8.1"
}
-
subscribeAsState()
هي دوال إضافية تحوّل مصادر البيانات التفاعلية في RxJava2 (مثلSingle
وObservable
وCompletable
) إلىState
في Compose.يجب توفُّر التبعية التالية في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.8.1"
}
-
subscribeAsState()
هي دوال إضافية تحوّل تدفقات RxJava3 التفاعلية (مثلSingle
وObservable
وCompletable
) إلىState
في Compose.يجب توفُّر التبعية التالية في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.8.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.8.1"
}
تتبُّع حالة الاتصال مقابل عدم تتبُّعها
تنشئ الدالة البرمجية القابلة للإنشاء التي تستخدم remember
لتخزين عنصر حالة داخلية،
ما يجعل الدالة البرمجية القابلة للإنشاء ذات حالة. HelloContent
هو مثال على دالة برمجية قابلة للإنشاء مع الاحتفاظ بالحالة لأنّها تحتفظ بحالتها name
وتعدّلها داخليًا. يمكن أن يكون ذلك مفيدًا في الحالات التي لا يحتاج فيها المتصل إلى التحكّم في الحالة ويمكنه استخدامها بدون الحاجة إلى إدارتها بنفسه. ومع ذلك، فإنّ الدوال البرمجية القابلة للإنشاء التي تتضمّن حالة داخلية تكون أقل قابلية لإعادة الاستخدام وأكثر صعوبة في الاختبار.
العنصر القابل للإنشاء بدون حالة هو عنصر قابل للإنشاء لا يحتفظ بأي حالة. تتمثّل إحدى الطرق السهلة لتحقيق ذلك في استخدام نقل الحالة.
أثناء تطوير دوال برمجية قابلة للإنشاء وقابلة لإعادة الاستخدام، غالبًا ما تريد عرض إصدارين من الدالة البرمجية القابلة للإنشاء نفسها، أحدهما يتضمّن بيانات الحالة والآخر لا يتضمّنها. تكون النسخة التي تحتفظ بالحالة مفيدة للمتصلين الذين لا يهتمون بالحالة، بينما تكون النسخة التي لا تحتفظ بالحالة ضرورية للمتصلين الذين يحتاجون إلى التحكّم في الحالة أو رفعها.
نقل القيمة
إنّ نقل الحالة في Compose هو نمط لنقل الحالة إلى العنصر الذي يستدعي العنصر القابل للإنشاء لجعل العنصر القابل للإنشاء بلا حالة. النمط العام لرفع الحالة في Jetpack Compose هو استبدال متغيّر الحالة بمعلَمتَين:
value: T
: القيمة الحالية التي سيتم عرضهاonValueChange: (T) -> Unit
: حدث يطلب تغيير القيمة، حيثT
هي القيمة الجديدة المقترَحة
ومع ذلك، لا يقتصر الأمر على onValueChange
. إذا كانت الأحداث الأكثر تحديدًا مناسبة للعنصر القابل للإنشاء، عليك تحديدها باستخدام تعبيرات lambda.
تتضمّن الحالة التي يتم نقلها بهذه الطريقة بعض الخصائص المهمة:
- مصدر واحد للحقيقة: من خلال نقل الحالة بدلاً من تكرارها، نضمن توفّر مصدر واحد للحقيقة. يساعد ذلك في تجنُّب الأخطاء.
- التغليف: يمكن فقط للعناصر القابلة للإنشاء ذات الحالة تعديل حالتها. وهي داخلية بالكامل.
- قابلة للمشاركة: يمكن مشاركة الحالة التي تم نقلها إلى مستوى أعلى مع عناصر متعددة قابلة للإنشاء. إذا أردت قراءة
name
في عنصر قابل للإنشاء مختلف، سيسمح لك النقل بذلك. - قابلة للاعتراض: يمكن للمتصلين بوظائف composable عديمة الحالة أن يقرروا تجاهل الأحداث أو تعديلها قبل تغيير الحالة.
- غير مرتبط: يمكن تخزين حالة العناصر القابلة للإنشاء عديمة الحالة في أي مكان. على سبيل المثال، أصبح من الممكن الآن نقل
name
إلىViewModel
.
في المثال، يمكنك استخراج name
وonValueChange
من HelloContent
ونقلهما إلى أعلى الشجرة إلى عنصر HelloScreen
قابل للإنشاء يستدعي HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
من خلال نقل الحالة إلى خارج HelloContent
، يصبح من الأسهل فهم العنصر القابل للإنشاء وإعادة استخدامه في مواقف مختلفة واختباره. HelloContent
منفصلة عن طريقة تخزين حالتها. يعني الفصل أنّه في حال تعديل HelloScreen
أو استبداله، لن تحتاج إلى تغيير طريقة تنفيذ HelloContent
.

يُطلق على النمط الذي تنخفض فيه الحالة وتزداد فيه الأحداث اسم تدفّق البيانات أحادي الاتجاه. في هذه الحالة، تنخفض الحالة من HelloScreen
إلى HelloContent
، وترتفع الأحداث من HelloContent
إلى HelloScreen
. من خلال اتّباع مسار البيانات أحادي الاتجاه، يمكنك فصل العناصر القابلة للإنشاء التي تعرض الحالة في واجهة المستخدم عن أجزاء تطبيقك التي تخزّن الحالة وتغيّرها.
يمكنك الاطّلاع على صفحة مكان نقل الحالة لمعرفة المزيد.
استعادة الحالة في Compose
يتشابه سلوك واجهة برمجة التطبيقات rememberSaveable
مع remember
لأنّه يحتفظ بالحالة على مستوى عمليات إعادة الإنشاء، وكذلك على مستوى إعادة إنشاء النشاط أو العملية باستخدام آلية حفظ حالة المثيل. على سبيل المثال، يحدث ذلك عند تدوير الشاشة.
طُرق تخزين الحالة
يتم حفظ جميع أنواع البيانات التي تتم إضافتها إلى Bundle
تلقائيًا. إذا أردت حفظ محتوى لا يمكن إضافته إلى Bundle
، تتوفّر لك عدة خيارات.
تقسيم
أبسط حل هو إضافة التعليق التوضيحي @Parcelize
إلى العنصر. يصبح العنصر قابلاً للتجزئة ويمكن تجميعه. على سبيل المثال، ينشئ هذا الرمز نوع بيانات City
قابل للتسلسل ويحفظه في الحالة.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
إذا لم يكن @Parcelize
مناسبًا لسبب ما، يمكنك استخدام mapSaver
لتحديد قاعدتك الخاصة لتحويل عنصر إلى مجموعة من القيم التي يمكن للنظام حفظها في Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
لتجنُّب الحاجة إلى تحديد مفاتيح الخريطة، يمكنك أيضًا استخدام listSaver
واستخدام فهارسها كمفاتيح:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
عناصر الاحتفاظ بالحالة في Compose
يمكن إدارة عملية نقل الحالة البسيطة في الدوال البرمجية القابلة للإنشاء نفسها. ومع ذلك، إذا زاد مقدار الحالة التي يجب تتبُّعها، أو إذا ظهرت الحاجة إلى تنفيذ منطق في الدوال القابلة للإنشاء، من الممارسات الجيدة تفويض مسؤوليات المنطق والحالة إلى فئات أخرى، وهي عناصر الاحتفاظ بالحالة.
لمزيد من المعلومات، يمكنك الاطّلاع على مستندات نقل الحالة في Compose أو، بشكل عام، على صفحة حاويات الحالة وحالة واجهة المستخدم في دليل التصميم.
إعادة تشغيل عمليات حساب التذكير عند تغيير المفاتيح
يتم استخدام واجهة برمجة التطبيقات remember
بشكل متكرّر مع MutableState
:
var name by remember { mutableStateOf("") }
في هذا المثال، يؤدي استخدام الدالة remember
إلى بقاء قيمة MutableState
بعد عمليات إعادة التركيب.
بشكل عام، تأخذ الدالة remember
مَعلمة lambda calculation
. عند تشغيل remember
للمرة الأولى، يتم استدعاء دالة lambda calculation
وتخزين نتيجتها. أثناء إعادة التركيب، تعرض الدالة remember
القيمة التي تم تخزينها آخر مرة.
بالإضافة إلى تخزين الحالة مؤقتًا، يمكنك أيضًا استخدام remember
لتخزين أي كائن أو نتيجة عملية في Composition يصعب تهيئتها أو حسابها. قد لا تحتاج إلى تكرار هذه العملية الحسابية في كل عملية إعادة تركيب.
أحد الأمثلة على ذلك هو إنشاء عنصر ShaderBrush
، وهي عملية مكلفة:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
تخزّن remember
القيمة إلى أن تغادر Composition. ومع ذلك، هناك طريقة لإبطال القيمة المخزّنة مؤقتًا. تتلقّى واجهة برمجة التطبيقات remember
أيضًا المَعلمة key
أو keys
. في حال تغيّر أيّ من هذه المفاتيح، سيؤدي ذلك إلى إعادة إنشاء الدالة في المرة التالية، وستؤدي remember
إلى إبطال صحة ذاكرة التخزين المؤقت وتنفيذ كتلة lambda الخاصة بالحساب مرة أخرى. تتيح لك هذه الآلية التحكّم في مدة بقاء عنصر في Composition. ويظل الحساب صالحًا إلى أن تتغير المدخلات، بدلاً من أن تنتهي صلاحية القيمة المحفوظة في Composition.
توضّح الأمثلة التالية طريقة عمل هذه الآلية.
في هذا المقتطف، يتم إنشاء ShaderBrush
واستخدامه كلون خلفية
لأحد العناصر القابلة للإنشاء Box
. يخزّن remember
مثيل ShaderBrush
لأنّ إعادة إنشائه مكلفة، كما شرحنا سابقًا. تستخدِم remember
avatarRes
كمعلَمة key1
، وهي صورة الخلفية المحدّدة. في حال تغيّر avatarRes
، تتم إعادة إنشاء الفرشاة باستخدام الصورة الجديدة، ثم يتم إعادة تطبيقها على Box
. يمكن أن يحدث ذلك عندما يختار المستخدم صورة أخرى لتكون
الخلفية من أداة اختيار.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
في المقتطف التالي، يتم نقل الحالة إلى فئة عادية لتخزين الحالة
MyAppState
. تعرض هذه السمة الدالة rememberMyAppState
لتهيئة مثيل للفئة باستخدام remember
. يُعد عرض هذه الدوال لإنشاء مثيل يبقى بعد إعادة التركيب نمطًا شائعًا في Compose. تتلقّى الدالة
rememberMyAppState
القيمة windowSizeClass
التي تعمل كمعلَمة
key
للدالة remember
. في حال تغيّر هذه المَعلمة، يجب أن تعيد التطبيق إنشاء فئة حامل الحالة العادية باستخدام أحدث قيمة. قد يحدث ذلك مثلاً إذا أدار المستخدم الجهاز.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
تستخدم Compose عملية تنفيذ equals الخاصة بالفئة لتحديد ما إذا كان المفتاح قد تغيّر وإبطال القيمة المخزّنة.
تخزين الحالة باستخدام مفاتيح تتجاوز إعادة التركيب
rememberSaveable
API هو برنامج تضمين لـ remember
يمكنه تخزين البيانات في Bundle
. تتيح واجهة برمجة التطبيقات هذه الحفاظ على الحالة ليس فقط عند إعادة التركيب، ولكن أيضًا عند إعادة إنشاء النشاط وإيقاف العملية من قِبل النظام.
تتلقّى rememberSaveable
المَعلمات input
للغرض نفسه الذي تتلقّى remember
المَعلمات keys
. يتم إبطال صحة ذاكرة التخزين المؤقت عند تغيير أي من المدخلات. في المرة التالية التي تتم فيها إعادة إنشاء الدالة، سيتم إعادة تنفيذ كتلة lambda الخاصة بالحساب rememberSaveable
.
في المثال التالي، يتم تخزين rememberSaveable
إلى أن يتغير userTypedQuery
typedQuery
:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
مزيد من المعلومات
لمزيد من المعلومات حول الحالة وJetpack Compose، يُرجى الاطّلاع على المراجع الإضافية التالية.
نماذج
الدروس التطبيقية حول الترميز
الفيديوهات
المدوّنات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة
- تصميم بنية واجهة مستخدم Compose
- حفظ حالة واجهة المستخدم في Compose
- الآثار الجانبية في Compose