الحالة في التطبيق هي أي قيمة يمكن أن تتغير بمرور الوقت. وهذا تعريف واسع للغاية ويشمل كل شيء من قاعدة بيانات الغرفة إلى متغير في الفئة.
تعرض جميع تطبيقات Android الحالة للمستخدم. في ما يلي بعض الأمثلة للحالة في تطبيقات Android:
- شريط Snackbar الذي يعرض الحالات التي يتعذّر فيها إنشاء اتصال بالشبكة
- مشاركة مدونة والتعليقات المرتبطة بها
- تموّج الرسوم المتحركة على الأزرار التي يتم تشغيلها عندما ينقر المستخدم عليها.
- ملصقات يمكن للمستخدم رسمها فوق صورة.
يساعدك 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.
لمزيد من المعلومات حول التأليف وإعادة الإنشاء، يمكنك الاطّلاع على مقالة التفكير في إنشاء المحتوى.
الحالة في العناصر القابلة للإنشاء
يمكن للدوال القابلة للإنشاء استخدام واجهة برمجة التطبيقات
remember
لتخزين كائن في الذاكرة. يتم تخزين قيمة تحسبها remember
في المقطوعة الموسيقية أثناء التركيبة الأولية، ويتم عرض القيمة المخزنة أثناء إعادة الإنشاء.
يمكن استخدام 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>
لتتمكّن العناصر القابلة للإنشاء من إعادة الإنشاء تلقائيًا عند تغيير الحالة.
يمكنك إنشاء مجموعات باستخدام الدوال لإنشاء State<T>
من الأنواع الشائعة القابلة للتتبّع المستخدمة في تطبيقات Android. قبل استخدام عمليات الدمج هذه، أضِف
الأدوات المناسبة كما هو موضّح أدناه:
Flow
:collectAsStateWithLifecycle()
تجمع حزمة
collectAsStateWithLifecycle()
القيم منFlow
بطريقة تراعي مراحل النشاط، ما يسمح لتطبيقك بالحفاظ على موارده. ويمثّل أحدث قيمة منبعثة من رمز الإنشاءState
. استخدِم واجهة برمجة التطبيقات هذه باعتبارها الطريقة الموصى بها لجمع التدفقات على تطبيقات Android.يجب توفير التبعية التالية في ملف
build.gradle
(يجب أن تكون بالإصدار 2.6.0 إلى الإصدار التجريبي 2.6.0 أو إصدار أحدث):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
}
رائع
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.2"
}
-
إنّ السمة
collectAsState
تشبه السمةcollectAsStateWithLifecycle
، لأنّها تجمع أيضًا القيم من السمةFlow
وتحوِّلها إلى سمة "إنشاء"State
.استخدِم
collectAsState
للحصول على رمز غير متوافق مع النظام الأساسي بدلاً منcollectAsStateWithLifecycle
، الذي يتوافق مع نظام Android فقط.لا حاجة إلى اعتماديات إضافية لـ
collectAsState
، لأنّها متوفّرة فيcompose-runtime
. -
تبدأ
observeAsState()
في ملاحظةLiveData
وتمثل قيمها من خلالState
.التبعية التالية مطلوبة في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.6.1")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.6.1"
}
-
subscribeAsState()
هي دوال إضافات تحوِّل عمليات البث التفاعلية لـ RxJava2 (مثلSingle
وObservable
وCompletable
) إلى ComposeState
.التبعية التالية مطلوبة في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.6.1")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.6.1"
}
-
subscribeAsState()
هي دوال إضافة تحوِّل عمليات البث التفاعلية لـ RxJava3 (مثلSingle
وObservable
وCompletable
) إلى ComposeState
.التبعية التالية مطلوبة في ملف
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.6.1")
}
رائع
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.6.1"
}
جدار الحماية المتتبِّع لحالة الاتصال في مقابل الحالة التي لا تتضمّن حالة
إنّ العنصر القابل للإنشاء الذي يستخدم remember
لتخزين عنصر ما يؤدي إلى إنشاء حالة داخلية، ما يجعل العنصر state القابل للإنشاء. 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
. باتّباع تدفق البيانات أحادي الاتجاه، يمكنك فصل العناصر القابلة للإنشاء التي تعرض الحالة في واجهة المستخدم عن أجزاء التطبيق التي يتم تخزينها وتغيير حالتها.
اطّلِع على صفحة مكان الرفع لمعرفة المزيد من المعلومات.
استعادة الحالة في 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")) } }
أداة حفظ الخرائط
إذا كانت السمة @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
معلَمة lambda calculation
. عند تشغيل remember
لأول مرة، يستدعي calculation
lambda وتخزِّن نتيجتها. أثناء إعادة التركيب، تعرض 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 طريقة تنفيذ يساوي للفئة لتحديد ما إذا كان المفتاح قد تغيّر أم لا ويؤدّي إلى إلغاء القيمة المخزنة.
حالة التخزين مع مفاتيح لا يمكن إعادة تركيبها
rememberSaveable
API هي أداة تضمين حول 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)) ) }
مزيد من المعلومات
لمعرفة مزيد من المعلومات حول State وJetpack Compose، يُرجى الاطّلاع على المراجع الإضافية التالية.
العيّنات
الدروس التطبيقية حول الترميز
الفيديوهات الطويلة
المدوّنات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- تصميم واجهة المستخدم في Compose
- حفظ حالة واجهة المستخدم في Compose
- التأثيرات الجانبية في Compose