الحالة في التطبيق هي أي قيمة يمكن أن تتغيّر بمرور الوقت. هذا تعريف واسع جدًا ويشمل كل شيء من قاعدة بيانات الغرفة إلى متغير في فئة ما.
تعرض جميع تطبيقات 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
في المقطوعة الموسيقية خلال
المقطوعة الموسيقية الأولية، ويتم عرض القيمة المخزنة أثناء إعادة التركيب.
يمكن استخدام remember
لتخزين كلّ من الكائنات القابلة للتغيير وغير القابلة للتغيير.
mutableStateOf
ينشئ MutableState<T>
نوعًا قابلاً للملاحظة ومتكاملاً مع وقت تشغيل إنشاء الرسالة.
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) }
هذه التعريفات متكافئة، ويتم تقديمها كطريقة سهلة لاستخدام بنية الجملة في استخدامات مختلفة لـ state. عليك اختيار الرمز الذي ينتج عن العبارة العبارة السهلة القراءة في العبارة المركبة التي تكتبها.
تتطلّب بنية التفويض 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
. وبالنسبة إلى القيم الأخرى، يمكنك تمرير كائن موفر مخصّص.
الأنواع الأخرى المتوافقة من الحالات
لا يتطلّب الإنشاء استخدام MutableState<T>
للاحتفاظ بالحالة، فهو يتيح استخدام أنواع أخرى قابلة للملاحظة. قبل قراءة نوع آخر من أنواع العناصر القابلة للتتبّع في
Compose، يجب تحويله إلى State<T>
حتى تتمكّن العناصر القابلة للتجميع من
إعادة التركيب تلقائيًا عند تغيُّر الحالة.
يتم شحن أداة الإنشاء مع دوال لإنشاء State<T>
من أنواع رصد
الشائعة المستخدَمة في تطبيقات Android. قبل استخدام عمليات الدمج هذه، أضِف العناصر المناسبة كما هو موضّح أدناه:
Flow
:collectAsStateWithLifecycle()
يجمع
collectAsStateWithLifecycle()
القيم منFlow
بطريقة الوعي بمراحل النشاط، ما يسمح لتطبيقك بالحفاظ على موارد التطبيق. وهي تمثل أحدث قيمة منبعثة من رمز الإنشاءState
. استخدِم واجهة برمجة التطبيقات هذه كطريقة مقترَحة لجمع بيانات مسارات الإحالات الناجحة على تطبيقات Android.يجب استخدام الملحق التالي في ملف
build.gradle
(يجب أن يكون الإصدار 2.6.0-beta01 أو إصدار أحدث):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
رائع
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
-
تكون
collectAsState
معادِلةcollectAsStateWithLifecycle
، لأنها تجمع أيضًا القيم منFlow
وتحولها إلى إنشاءState
.استخدِم
collectAsState
للرمز البرمجي غير المرتبط بالنظام الأساسي بدلاً منcollectAsStateWithLifecycle
المخصّص لنظام Android فقط.لا يلزم توفُّر تبعيات إضافية لـ
collectAsState
، لأنّه متاح فيcompose-runtime
. -
يبدأ
observeAsState()
بمراقبة هذاLiveData
ويمثّل قيمه من خلالState
.يجب إدراج التبعية التالية في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.5")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.5"
}
-
subscribeAsState()
هي دوالّ إضافية تحوّل تدفقات التفاعلية في RxJava2 (مثلSingle
وObservable
Completable
) إلى ComposeState
.يجب إدراج التبعية التالية في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.5")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.5"
}
-
subscribeAsState()
هي دوالّ إضافية تحوّل تدفقات التفاعلية في RxJava3 (مثلSingle
وObservable
Completable
) إلى ComposeState
.الاعتمادية التالية مطلوبة في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.5")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.5"
}
حالة تسجيل الدخول في مقابل الحالة غير المُسجّلة
إنّ العنصر القابل للتجميع الذي يستخدم remember
لتخزين عنصر ينشئ حالة داخلية،
ما يجعل العنصر القابل للتجميع يعتمد على الحالة. HelloContent
هو مثال على حالة قابلة للإنشاء
لأنّها تحتفظ بحالة name
الخاصة بها وتعدِّلها داخليًا. يمكن أن يكون ذلك مفيداً في الحالات التي لا يحتاج فيها المتصل إلى التحكّم في الحالة ويمكنه استخدامها بدون الحاجة إلى إدارة الحالة بنفسه. ومع ذلك، تميل المواد القابلة للإنشاء ذات الحالة الداخلية إلى أن تكون أقل قابلية لإعادة الاستخدام ويصعب اختبارها.
عنصر بدون حالة قابل للإنشاء هو عنصر قابل للإنشاء لا يحمل أي حالة. إنّ إحدى الطرق السهلة لتحقيق وظائف بدون حالة هي استخدام حالة الرفع.
أثناء تطوير مكونات قابلة لإعادة الاستخدام، غالبًا ما تريد عرض إصدار يعتمد على الحالة وإصدار لا يعتمد على الحالة من المكوّن نفسه. يكون الإصدار الذي يتضمّن حالة مناسبًا للمتصلين الذين لا يهتمون بالحالة، ويكون الإصدار الذي لا يتضمّن حالة ضروريًا للمتصلين الذين يحتاجون إلى التحكّم في الحالة أو رفعها.
نقل القيمة
إنّ تصعيد الحالة في Compose هو نمط لنقل الحالة إلى المُرسِل الذي يُنشئ العنصر القابل للتجميع لإلغاء حالة العنصر القابل للتجميع. النمط العام لرفع الحالة في Jetpack Compose هو استبدال متغيّر الحالة بمعاملتَين:
value: T
: القيمة الحالية المطلوب عرضهاonValueChange: (T) -> Unit
: حدث يطلب تغيير القيمة، حيث يكونT
هو القيمة الجديدة المقترَحة
ومع ذلك، يمكنك استخدام onValueChange
أو أيّ تنسيق آخر. إذا كانت الأحداث الأكثر تحديدًا مناسبة للعنصر القابل للإنشاء،
يجب عليك تحديدها باستخدام lambdas.
إنّ الحالة التي يتمّ رفعها بهذه الطريقة لها بعض السمات المهمة:
- مصدر واحد للحقيقة: من خلال نقل الحالة بدلاً من تكرارها، نضمن توفّر مصدر واحد للحقيقة. ويساعد ذلك في تجنّب الأخطاء.
- مغلف: يمكن فقط للعناصر القابلة للإنشاء التي لها حالة إنشاء تعديل حالتها. إنه داخلي تمامًا.
- قابلة للمشاركة: يمكن مشاركة الحالة المُرفوعة مع عناصر قابلة للتجميع متعددة. إذا أردت قراءة
name
في عنصر مركب مختلف، ستسمح لك عملية الرفع بقراءة ذلك. - غير قابل للإنشاء: يمكن للمتصلين إلى العناصر القابلة للإنشاء التي لا تتضمّن حالة اختيار تجاهل الأحداث أو تعديلها قبل تغيير الحالة.
- غير مرتبطة: يمكن تخزين حالة العناصر القابلة للتجميع التي لا تتضمّن حالة في أي مكان. على سبيل المثال، أصبح من الممكن الآن نقل
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
. من خلال
اتّباع تدفق البيانات أحادي الاتجاه، يمكنك فصل العناصر القابلة للتجميع التي تعرِض
الحالة في واجهة المستخدم عن أجزاء تطبيقك التي تخزِّن الحالة وتغيّرها.
اطّلِع على صفحة أماكن رفع الحالة لمعرفة المزيد من المعلومات.
استعادة الحالة في ميزة "الإنشاء"
تعمل واجهة برمجة التطبيقات rememberSaveable
بالطريقة نفسها التي تعمل بها remember
لأنّها تحافظ على الحالة في عمليات إعادة التركيب، وكذلك في عمليات إعادة إنشاء النشاط أو العملية باستخدام آلية حالة النسخة المحفوظة. على سبيل المثال، يحدث هذا عندما
يتم تدوير الشاشة.
طُرق تخزين الحالة
يتم تلقائيًا حفظ جميع أنواع البيانات التي تتم إضافتها إلى Bundle
. إذا أردت
حفظ عنصر لا يمكن إضافته إلى Bundle
، تتوفّر عدة
خيارات.
Parcelize
أبسط حلّ هو إضافة التعليق التوضيحي
@Parcelize
إلى الجسم. يصبح العنصر قابلاً للتقسيم ويمكن تجميعه. على سبيل المثال، تنشئ هذه التعليمة البرمجية نوع بيانات City
قابلاً للتقسيم وتحفظه في القيمة
state.
@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
واستخدام مؤشراتها كمفاتيح:
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
مَعلمة لامدا calculation
. عند تشغيل remember
للمرة الأولى، يتمّ استدعاء دالة lambda calculation
وتخزين نتيجتها. أثناء
إعادة التركيب، تعرض remember
القيمة التي تم تخزينها آخر مرة.
بخلاف حالة التخزين المؤقت، يمكنك أيضًا استخدام remember
لتخزين أي عنصر أو
نتيجة لعملية في المقطوعة الموسيقية يكون إعدادها أو
احتسابها مكلفًا. قد لا تحتاج إلى تكرار هذا الحساب في كل عملية إعادة تركيب.
على سبيل المثال، إنشاء عنصر ShaderBrush
هذا، وهو عملية
مكلفة:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
تخزِّن remember
القيمة إلى أن تغادر التركيب. ومع ذلك، هناك
طريقة لإبطال القيمة المخزّنة مؤقتًا. تستخدم واجهة برمجة التطبيقات remember
أيضًا المَعلمة key
أو keys
. إذا تغيّر أيّ من هذه المفاتيح، في المرة التالية التي تتم فيها
إعادة تركيب الدالة، تبطل remember
ذاكرة التخزين المؤقت وتنفِّذ عملية الحسابات
وحدة lambda مرة أخرى. تمنحك هذه الآلية إمكانية التحكّم في مدة بقاء
العنصر في التركيب. وتظل العملية الحسابية صالحة إلى أن تتغير الإدخالات، بدلاً من أن تغادر القيمة التي تم تذكرها المقطوعة الموسيقية.
توضّح الأمثلة التالية آلية عمل هذه الآلية.
في هذا المقتطف، تم إنشاء 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
هي برنامج تضمين حول remember
ويمكنه تخزين البيانات في Bundle
. لا تسمح واجهة برمجة التطبيقات هذه ببقائها في حالة إعادة التركيب فحسب، بل تسمح أيضًا بإعادة إنشاء النشاط وإيقاف العملية التي بدأها النظام.
تتلقّى rememberSaveable
مَعلمات input
للغرض نفسه الذي تتلقّى فيه
remember
مَعلمات keys
. تصبح ذاكرة التخزين المؤقت غير صالحة عند
تغيُّر أيّ من المدخلات. في المرة التالية التي تتم فيها إعادة إنشاء الدالة، تُعيد rememberSaveable
تنفيذ كتلة lambda الحسابية.
في المثال التالي، يخزِّن rememberSaveable
القيمة userTypedQuery
إلى أن يتم تغيير
typedQuery
:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
مزيد من المعلومات
لمزيد من المعلومات عن الحالة وJetpack Compose، يمكنك الرجوع إلى المراجع التالية الإضافية.
نماذج
الدروس التطبيقية حول الترميز
الفيديوهات
المدوّنات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تصميم واجهة مستخدِم ميزة "الكتابة الذكية"
- حفظ حالة واجهة المستخدم في Compose
- الآثار الجانبية في ميزة "الإنشاء"