مقدمة: "لقد فعلت كل شيء حسب الكتاب المدرسي، لكن التطبيق يتعطل!"
\nإذا كنت قد بدأت للتو رحلتك في تطوير Android باستخدام Kotlin، فمن المحتمل أنك واجهت هذه اللحظة بالفعل. تكتب الكود، متبعًا برنامجًا تعليميًا، وتشغل التطبيق... و تعطل. في السجلات — java.lang.NullPointerException الشهير (أو ما يعادله في Kotlin). والتوقيع: "لا يمكن قراءة الحقل... لأن العرض (view) فارغ".
هذا ألم حقيقي جمع آلاف المشاهدات على Stack Overflow. اليوم سنقوم بتحليله مرة واحدة وإلى الأبد. لماذا يحدث NullPointerException عند الوصول إلى View داخل Fragment في Kotlin؟ والأهم، كيف نصلحه لتنام مرتاح البال.
\nهيا بنا!
\n\nلماذا يحدث الخطأ؟ جذر الشر — دورة الحياة (Lifecycle)
\nالسبب الرئيسي الذي يجعل المبتدئين يحصلون على NullPointerException في Fragment في Kotlin هو عدم فهم دورة حياة الجزء (Fragment lifecycle). في النشاط (Activity) الأمر بسيط: onCreate — وانطلق. في الجزء (Fragment) الأمر أكثر تعقيدًا.
يمر الجزء بعدة مراحل:
\n- \n
onCreateView()— هنا يتم إنشاء (تضخيم) التخطيط. \n onViewCreated()— هنا يكون العرض (View) موجودًا بالتأكيد. \n onDestroyView()— يتم تدمير العرض (View) (على سبيل المثال، عند تدوير الشاشة). \n
الخطأ الأكثر شيوعًا هو محاولة الوصول إلى العرض (View) قبل أن يقوم الجزء بإنشاء تخطيطه. على سبيل المثال، في المُنشئ (constructor) أو في onCreate() للجزء. في هذه اللحظة، view يساوي null — ومن هنا يأتي التعطل.
السبب الثاني هو تسرب المراجع (reference leak). لقد حفظت مرجعًا للعرض (View) في متغير، ثم أعيد إنشاء الجزء، وأصبح المرجع القديم يؤدي إلى لا شيء.
\n\nمثال نموذجي: كيف تبدو المشكلة في الكود
\nتخيل أن لديك جزءًا بسيطًا مع حقل نصي:
\nclass MyFragment : Fragment() {\n\n private lateinit var myTextView: TextView\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n // خطأ! العرض غير موجود بعد هنا\n myTextView = requireView().findViewById(R.id.my_text_view)\n myTextView.text = "مرحبًا!"\n }\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? {\n return inflater.inflate(R.layout.fragment_my, container, false)\n }\n}\nسيتعطل هذا الكود مع NullPointerException، لأن requireView() في onCreate() يُرجع null. لم يتم إنشاء العرض (View) بعد!
3 طرق عملية لإصلاح الخطأ
\nدعنا نلقي نظرة على ثلاث طرق من شأنها أن تحل بشكل مضمون مشكلة الوصول إلى View داخل Fragment.
\n\nالطريقة 1: نقل الكود إلى onViewCreated()
\nأبسط وأصح طريقة هي انتظار اللحظة التي يكون فيها العرض (View) موجودًا بالتأكيد. يتم استدعاء الدالة onViewCreated() فور إنشاء التخطيط.
class MyFragment : Fragment() {\n\n private lateinit var myTextView: TextView\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? {\n return inflater.inflate(R.layout.fragment_my, container, false)\n }\n\n override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n super.onViewCreated(view, savedInstanceState)\n // الآن العرض (view) ليس null بالتأكيد!\n myTextView = view.findViewById(R.id.my_text_view)\n myTextView.text = "كل شيء يعمل!"\n }\n}\nالإيجابيات: بسيط، واضح، يعمل دائمًا.
\nالسلبيات: تحتاج إلى تذكر استخدام lateinit var وعدم نسيان إفراغ المراجع في onDestroyView() (وإلا قد يحدث تسرب للذاكرة).