بناء مكون التنقل (Navigation Component) في تطبيقات أندرويد: دليل شامل للمطورين

دقائق القراءة: 7

مقدمة إلى مكون التنقل (Navigation Component) في أندرويد

لطالما كان تصميم واجهات المستخدم في التطبيقات مهمة معقدة، وغالباً ما تتضمن رسومات تخطيطية توضح مسارات متعددة وتفاعلات المستخدم المعقدة. ما قد يبدو في البداية كتطبيق بسيط يحتوي على نشاط واحد أو اثنين، سرعان ما يتطور ليضم تدفقات متعددة، وشاشات (fragments)، ونطاقاً واسعاً من التفاعلات. ألن يكون رائعاً لو تمكنا من تحويل هذه المخططات المعقدة إلى تعليمات برمجية بسهولة وفعالية؟ هنا يأتي دور Navigation Component.

لماذا نحتاج مكون التنقل؟

بالنسبة للمطورين الجدد، قد لا يكون Navigation Component مجرد فئة واجهة مستخدم أخرى تضعها بدلاً من التخطيط (layout) لنشاطك أو شاشتك. تخيل الأمر كخريطة، حيث بدلاً من القارات، لديك شاشاتك (fragments)، وستحتاج إلى توجيهات واضحة للانتقال من شاشة إلى أخرى. يقدم هذا المكون شاشاتك والاتصالات بينها بطريقة منظمة ومن الأعلى إلى الأسفل، مما يسهل تصور تدفق المستخدم وإدارته.

ما هو مكون التنقل؟

Navigation Component هو جزء من مكتبة Android Jetpack، ويهدف إلى تبسيط عملية التنقل بين الشاشات المختلفة داخل التطبيق. يوفر إطار عمل موحداً لإدارة التنقل، سواء كان ذلك بين الشاشات (fragments)، أو الأنشطة (activities)، أو حتى الوجهات المخصصة. في هذا المقال، سنتعمق في الجوانب الرئيسية لهذا المكون ونتعلم كيفية دمجه في تطبيقاتنا. هل أنت مستعد للإبحار في عالم التنقل السلس؟ ⛵️

الشروع في العمل: تهيئة بيئة التطوير

مكون التنقل متاح بدءاً من Android Studio 3.3 والإصدارات الأحدث. لاستخدامه، ستحتاج إلى إضافة التبعيات التالية إلى ملف build.gradle الخاص بمشروعك (عادةً ملف app/build.gradle):

إضافة التبعيات اللازمة

android {
    ...
}
dependencies {
    implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'
}

تصور هيكل التطبيق

لتوضيح كيفية عمل المكون، دعنا نتخيل أننا صممنا تطبيقاً بالهيكل التالي:

  • Start Fragment (شاشة البداية)
  • Fragment A (الشاشة أ)
  • Fragment B (الشاشة ب)

يمكن للمستخدم الانتقال إما إلى Fragment A أو Fragment B من Start Fragment.

رسم توضيحي لهيكل التطبيق المقترح مع شاشة البداية وشاشتين فرعيتين

إذا أردنا تنفيذ كل هذا بدون Navigation Component، فسيتعين علينا إضافة التعليمات البرمجية المألوفة لفتح شاشة عند النقر على أحد الأزرار، مثل:

val myFragment : MyFragment = MyFragment()
supportFragmentManager.beginTransaction().add(R.id.container, myFragment).commit()

في مثالنا الصغير هذا، يبدو الأمر بسيطاً ولا يتجاوز بضعة أسطر، لكننا نتفق جميعاً أن هذا النهج لن يكون فعالاً أو قابلاً للتطوير إذا كان تطبيقنا أكبر ويحتوي على تدفقات مستخدم أكثر تعقيداً. هنا تبرز قوة Navigation Component في تبسيط هذه العملية.

بناء الرسم البياني للتنقل (Navigation Graph)

للبدء في استخدام Navigation Component، نحتاج إلى إنشاء رسم بياني للتنقل (navigation graph). سيعمل هذا الرسم البياني كخريطتنا، يحدد تدفق المستخدم في تطبيقنا.

إنشاء ملف الرسم البياني

لإنشاء ملف الرسم البياني، انقر بزر الماوس الأيمن على مجلد res، ثم اختر New > Android Resource File. سنسمي ملفنا user_flow_graph.xml. تأكد من تحديد نوع الملف كـ Navigation.

شاشة إنشاء ملف موارد جديد في Android Studio مع تحديد نوع Navigation

دمج مضيف التنقل (NavHostFragment) في الواجهة

كل رحلة تبدأ من نقطة انطلاق، ورحلتنا لا تختلف. نقطة الانطلاق لدينا تسمى NavHost. سيعمل هذا كعنصر نائب (placeholder) للوجهات ليتم تبديلها عندما يتفاعل المستخدم مع واجهة المستخدم الخاصة بنا. نحتاج إلى إضافة NavHost إلى التخطيط الرئيسي لنشاطنا (عادةً ملف activity_main.xml):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/user_flow_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

لقد أضفنا عنصر <fragment> الذي سيستضيف الشاشات التي سيتم عرضها وتبديلها. انتبه إلى السمة app:navGraph، التي ربطناها بملف XML الذي أنشأناه مسبقاً.

إضافة الوجهات (Destinations) إلى الرسم البياني

الآن، نحتاج إلى إضافة وجهة بدء، حيث لن يتم تجميع تطبيقنا إذا لم نفعل ذلك. مع فتح ملف user_flow_graph.xml، نحتاج إلى النقر على أيقونة الزائد الصغيرة في محرر التنقل (Navigation Editor):

شاشة محرر التنقل في Android Studio مع زر إضافة وجهة جديدة

يمكنك أن ترى في القائمة المنبثقة أنه يمكننا إما إنشاء عنصر نائب (placeholder) سيتعين ملؤه لاحقاً، أو يمكننا الاختيار من أي شاشة (fragment) لدينا:

خيارات إضافة وجهة جديدة في محرر التنقل، إما Placeholder أو Fragment موجود

يبدأ تدفق المستخدم لدينا من Start Fragment، لذا دعنا نختاره أولاً.

شاشة البداية (Start Fragment) المضافة إلى الرسم البياني للتنقل

الآن، دعنا نضيف شاشتينا الأخريين، Fragment A و Fragment B.

جميع الوجهات (Start, Fragment A, Fragment B) مضافة إلى الرسم البياني للتنقل

ربط الوجهات: مفهوم الإجراءات (Actions)

نقوم بربط وجهتين بالنقر على النقطة التي تظهر عندما نحوم فوق وجهة ما، وسحبها إلى وجهة أخرى.

فيديو متحرك يوضح كيفية ربط الوجهات في محرر التنقل

ما أنشأناه للتو بين Start Fragment و Fragment A و Fragment B هي إجراءات (actions). هذه الإجراءات هي التي تحدد المسارات الممكنة بين الوجهات.

التنقل برمجياً: تفعيل الإجراءات

قد تتساءل، هل بمجرد ربط الوجهات ينتهي عملنا ويعمل كل شيء بطريقة سحرية؟ حسناً، لن يحدث ذلك. نحتاج إلى إخبار تعليماتنا البرمجية بالانتقال إلى وجهة معينة. فكيف يمكننا فعل ذلك؟

تأمين التنقل باستخدام Safe Args

الخطوة الأولى هي إضافة مكون إضافي (gradle plugin) يسمى Safe Args. سيضمن هذا المكون الأمان من حيث النوع (type safety) عندما نتنقل بين وجهاتنا. أضف الكود التالي إلى ملف build.gradle الخاص بالمشروع (عادةً ملف project/build.gradle):

buildscript {
    /...}
    dependencies {
        ...
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.0.0"
    }
}

سنحتاج أيضاً إلى إضافة المكون الإضافي التالي إلى ملف build.gradle الخاص بالتطبيق (عادةً ملف app/build.gradle):

apply plugin: "androidx.navigation.safeargs.kotlin"

تأكد أيضاً من أن السطر android.useAndroidX=true موجود في ملف gradle.properties الخاص بك.

توليد التعليمات البرمجية للإجراءات

قبل المضي قدماً، دعنا نفهم لماذا احتجنا إلى إضافة كل هذه التكوينات. بشكل أساسي، عندما أنشأنا الإجراءات سابقاً، يقوم Android Studio، خلف الكواليس، بتوليد تعليمات برمجية سنستخدمها لتفعيل هذه الإجراءات. تتكون هذه التعليمات البرمجية من دوال وفئات تمثل كل إجراء.

لنأخذ Start Fragment كمثال. ستتضمن التعليمات البرمجية المولدة للإجراءات التي أعلناها فئة تسمى StartFragmentDirections. تمثل دوال هذه الفئة الإجراءات التي أنشأناها سابقاً. لذا، بالنسبة لشاشتينا، سنحصل على:

  • StartFragmentDirections.actionStartFragmentToFragmentA()
  • StartFragmentDirections.actionStartFragmentToFragmentB()

الآن بعد أن تم ترجمة إجراءاتنا إلى تعليمات برمجية، دعنا نستخدمها:

val action = StartFragmentDirections.actionStartFragmentToFragmentA()

استخدام NavController لإدارة التنقل

الخطوة الأخيرة في هذه العملية تتطلب منا استخدام NavController. هذا الكائن مسؤول عن إدارة التنقل داخل NavHost الخاص بنا. يمكنك الوصول إليه باستخدام إحدى هذه الطرق الثلاث:

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

لذا، مجتمعين سيكون لدينا:

fragmentABtn.setOnClickListener { button ->
    val action = StartFragmentDirections.actionStartFragmentToFragmentA()
    button.findNavController().navigate(action)
}

هذا الكود يقوم بتحديد الإجراء المطلوب ثم يستخدم NavController لتنفيذ عملية التنقل.

تمرير البيانات بين الوجهات (Destination Arguments)

ماذا لو أردنا تمرير بيانات بين وجهاتنا؟ تخيل سيناريو حيث إذا نقر المستخدم على عنصر معين، فإننا نريد أن نفعل شيئاً بهذا العنصر في وجهتنا التالية. لهذا الغرض، لدينا وسائط الوجهة (destination arguments).

تعريف الوسائط في الرسم البياني

افتح ملف user_flow_graph.xml وانقر على Fragment A. ستلاحظ في الجانب الأيمن قائمة تفصل السمات المختلفة لـ Fragment A. إحدى هذه السمات ستكون Arguments (الوسائط).

واجهة محرر التنقل تظهر قسم Arguments لـ Fragment A

لإضافة وسيط، ما عليك سوى النقر على أيقونة . ستفتح نافذة منبثقة يمكننا فيها تكوين وسيطنا. يمكنك إعطائه اسماً، واختيار نوعه، وإضافة قيمة افتراضية. دعنا نضيف وسيطاً من نوع String إلى Fragment A، والذي سيكون الرسالة التي يتم تمريرها من Start Fragment.

نافذة إضافة وسيط جديد لـ Fragment A مع تحديد الاسم والنوع والقيمة الافتراضية

تمرير الوسائط برمجياً

في Start Fragment، حيث قمنا بتعريف الإجراء ونقوم باستدعاء الدالة المولدة، سنمرر وسيطنا:

fragmentABtn.setOnClickListener { button ->
    val action = StartFragmentDirections.actionStartFragmentToFragmentA("Hello From Start Fragment")
    button.findNavController().navigate(action)
}

استقبال الوسائط في الوجهة المستهدفة

للوصول إلى الوسيط في Fragment A، سنحتاج إما إلى:

  1. الوصول إلى الحزمة (bundle) والحصول على قيمة رسالتنا:
class FragmentA : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val bundle = arguments
        val root = inflater.inflate(R.layout.fragment_a, container, false)
        val textView : TextView = root.findViewById(R.id.textView)
        textView.text = bundle?.getString("message")
        return root
    }
}
  1. استخدام navArgs إذا كنا نستخدم تبعيات -ktx:
class FragmentA : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val args : FragmentAArgs by navArgs()
        val root = inflater.inflate(R.layout.fragment_a, container, false)
        val textView : TextView = root.findViewById(R.id.textView)
        textView.text = args.message
        return root
    }
}

✋ عند استخدام navArgs، ستحتاج إلى إضافة دعم Java8 في ملف build.gradle الخاص بك.

يمكنك العثور على جميع التعليمات البرمجية المعروضة هنا في مستودع GitHub هذا.

الخلاصة التقنية

يُعد Android Navigation Component إضافة قوية وحيوية لمجموعة أدوات المطورين، حيث يعالج أحد التحديات الأكثر شيوعاً في تطوير تطبيقات أندرويد: إدارة تدفق المستخدم المعقد. من خلال توفير طريقة مرئية ومنظمة لتحديد الوجهات والإجراءات، فإنه يقلل بشكل كبير من التعليمات البرمجية المتكررة (boilerplate code) المرتبطة بالتعامل اليدوي مع FragmentTransactions. كما أن دمج Safe Args يعزز أمان النوع، مما يقلل من الأخطاء المحتملة عند تمرير البيانات بين الشاشات. يساهم هذا المكون في تحسين قابلية الصيانة وقابلية التوسع للتطبيقات، مما يجعله خياراً مفضلاً لتطوير تطبيقات أندرويد حديثة وفعالة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *