NullPointerException في جزء Kotlin: كيف تتغلب عليه وتحافظ على هدوئك

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: "لقد فعلت كل شيء حسب الكتاب المدرسي، لكن التطبيق يتعطل!"

\n

إذا كنت قد بدأت للتو رحلتك في تطوير Android باستخدام Kotlin، فمن المحتمل أنك واجهت هذه اللحظة بالفعل. تكتب الكود، متبعًا برنامجًا تعليميًا، وتشغل التطبيق... و تعطل. في السجلات — java.lang.NullPointerException الشهير (أو ما يعادله في Kotlin). والتوقيع: "لا يمكن قراءة الحقل... لأن العرض (view) فارغ".

\n

هذا ألم حقيقي جمع آلاف المشاهدات على Stack Overflow. اليوم سنقوم بتحليله مرة واحدة وإلى الأبد. لماذا يحدث NullPointerException عند الوصول إلى View داخل Fragment في Kotlin؟ والأهم، كيف نصلحه لتنام مرتاح البال.

\n

هيا بنا!

\n\n

لماذا يحدث الخطأ؟ جذر الشر — دورة الحياة (Lifecycle)

\n

السبب الرئيسي الذي يجعل المبتدئين يحصلون على NullPointerException في Fragment في Kotlin هو عدم فهم دورة حياة الجزء (Fragment lifecycle). في النشاط (Activity) الأمر بسيط: onCreate — وانطلق. في الجزء (Fragment) الأمر أكثر تعقيدًا.

\n

يمر الجزء بعدة مراحل:

\n
    \n
  • onCreateView() — هنا يتم إنشاء (تضخيم) التخطيط.
  • \n
  • onViewCreated() — هنا يكون العرض (View) موجودًا بالتأكيد.
  • \n
  • onDestroyView() — يتم تدمير العرض (View) (على سبيل المثال، عند تدوير الشاشة).
  • \n
\n

الخطأ الأكثر شيوعًا هو محاولة الوصول إلى العرض (View) قبل أن يقوم الجزء بإنشاء تخطيطه. على سبيل المثال، في المُنشئ (constructor) أو في onCreate() للجزء. في هذه اللحظة، view يساوي null — ومن هنا يأتي التعطل.

\n

السبب الثاني هو تسرب المراجع (reference leak). لقد حفظت مرجعًا للعرض (View) في متغير، ثم أعيد إنشاء الجزء، وأصبح المرجع القديم يؤدي إلى لا شيء.

\n\n

مثال نموذجي: كيف تبدو المشكلة في الكود

\n

تخيل أن لديك جزءًا بسيطًا مع حقل نصي:

\n
class 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) بعد!

\n\n

3 طرق عملية لإصلاح الخطأ

\n

دعنا نلقي نظرة على ثلاث طرق من شأنها أن تحل بشكل مضمون مشكلة الوصول إلى View داخل Fragment.

\n\n

الطريقة 1: نقل الكود إلى onViewCreated()

\n

أبسط وأصح طريقة هي انتظار اللحظة التي يكون فيها العرض (View) موجودًا بالتأكيد. يتم استدعاء الدالة onViewCreated() فور إنشاء التخطيط.

\n
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() (وإلا قد يحدث تسرب للذاكرة).

\n\n

الطريقة 2: استخدام View Binding (موصى به)

\n

المدونات

توصيات الكتب