ذعر صامت: عندما يصرخ Kotlin بأن State لا يمكن أن يكون مفوضًا
\n\nأنت تكتب كودًا بلغة Kotlin، وتستخدم Jetpack Compose، وكل شيء يسير وفقًا للخطة. وفجأة - بام! - يصدر المترجم رسالة تجعل المبتدئ يشعر ببرودة في بطنه: Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate. تبدو وكأنها حكم بالإعدام، لكنها في الواقع مجرد صرخة استغاثة من الكود الخاص بك. دعنا نفهم ما يحاول قوله وكيف نتصالح معه.
هذا الخطأ هو واحد من أشهر الأخطاء على Stack Overflow (آلاف المشاهدات!)، وهو يطارد المطورين الذين بدأوا للتو في تعلم الخصائص المفوضة في Kotlin. في هذه المقالة، لن نترجم رسالة الخطأ فحسب، بل سنعرض ثلاثة سيناريوهات حقيقية يظهر فيها الخطأ، ونقدم حلولًا عملية. ستتعلم كيفية استخدام StateFlow في Compose بشكل صحيح، وكيف لا تتشوش في مفوضات Kotlin، ولماذا يتصرف LiveData أحيانًا بشكل غير متوقع.
\n\nما هو المفوض في Kotlin ولماذا لا يصبح State واحدًا؟
\n\nبداية - شرح سريع. الخصائص المفوضة في Kotlin (الكلمة المفتاحية by) تسمح بنقل منطق getter و setter إلى كائن منفصل. على سبيل المثال:
var name: String by Delegates.observable("") { prop, old, new ->\n println("$old → $new")\n}\n\n\nلكي يتمكن الكائن من أن يكون مفوضًا، يجب أن ينفذ الطريقتين getValue() وربما setValue() بتوقيع معين. الخطأ Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' يشير تحديدًا إلى هذا: الكائن الخاص بك من النوع State<List<User>?> لا يحتوي على طريقة getValue مناسبة. لا يمكن للمترجم استخدامه كمفوض.
الآن ننتقل إلى ثلاثة سيناريوهات محددة يظهر فيها هذا الخطأ في أغلب الأحيان.
\n\nالخطأ 1: الخلط بين by وتعيين StateFlow
\n\nالحالة الأكثر شيوعًا - عندما يحاول المطور استخدام by لتعيين StateFlow في ViewModel، لكنه يفعل ذلك بشكل خاطئ. انظر إلى هذا الكود:
// ❌ خطأ! لا يمكن فعل ذلك\nclass MyViewModel : ViewModel() {\n private val _users = MutableStateFlow<List<User>?>(null)\n val users: StateFlow<List<User>?> by _users // خطأ!\n}\n\n\nهنا _users هو MutableStateFlow، ونحن نحاول جعله مفوضًا للخاصية users. لكن StateFlow لا ينفذ getValue لخصائص Kotlin. إنه ليس مفوضًا، بل تدفق تفاعلي.
الحل: استخدم asStateFlow()
\n\nالطريقة الصحيحة - ليست التفويض، بل التحويل:
\n\n// ✅ صحيح\nclass MyViewModel : ViewModel() {\n private val _users = MutableStateFlow<List<User>?>(null)\n val users: StateFlow<List<User>?> = _users.asStateFlow()\n}\n\n\nالطريقة asStateFlow() تنشئ نسخة غير قابلة للتغيير يمكن عرضها بأمان للخارج. لا مفوضات، لا أخطاء.
الخطأ 2: استخدام by مع LiveData في Compose
\n\nثاني أشهر حالة - محاولة استخدام LiveData مباشرة مع by في Jetpack Compose. المطورون القادمون من عالم أنظمة View اعتادوا على LiveData ويحاولون تطبيق نفس النهج:
// ❌ خطأ!\n@Composable\nfun UserList(viewModel: MyViewModel) {\n val users: List<User>? by viewModel.users // خطأ!\n // ...\n}\n\n\nهنا viewModel.users من النوع LiveData<List<User>?>. و LiveData ليس مفوضًا لخصائص Compose. المترجم يعطي نفس الخطأ مرة أخرى: Type 'LiveData<List<User>?>' has