کامپوننت Navigation در اندروید — از صفر تا صد

۴۶۱ بازدید
آخرین به‌روزرسانی: ۰۳ مهر ۱۴۰۲
زمان مطالعه: ۸ دقیقه
کامپوننت Navigation در اندروید — از صفر تا صد

در اندروید به طور کلی منطق ناوبری با استفاده از Intent برای جابجایی بین اکتیویتی‌ها و با fragment transactions برای ناوبری بین فرگمان‌ها کدنویسی می‌شود. کامپوننت Navigation Architecture گوگل موجب شده ناوبری در اپلیکیشن‌های اندرویدی آسان‌تر شود. در این مقاله به بررسی کامپوننت Navigation در اندروید می‌پردازیم و در این مسیر با هر دو روش ابتدایی و پیشرفته استفاده از این کامپوننت آشنا خواهیم شد.

مشکل چه بود؟

زمانی که اپلیکیشن‌هایی با چند فرگمان توسعه می‌دهیم، معمولاً تراکنش‌های فرگمانی زیادی برای ناوبری بین آن‌ها مورد نیاز است. نوشتن این تراکنش‌های فرگمانی و مدیریت back stack به تلاش زیادی نیاز دارد. اگر این کار را به روش درستی انجام ندهید، مشکل عمده دیگر که رخ می‌دهد، IllegalStateException است.

راه‌حل

برای این که ناوبری آسان‌تر باشد، گوگل کامپوننت Navigation را معرفی کرده است. به کمک این کامپوننت می‌توان به سادگی ناوبری بین فرگمان‌ها و مدیریت مواردی از قبیل back stack، موارد استثنا و غیره را مدیریت کرد. در بخش بعدی کار با کامپوننت Navigation را آغاز می‌کنیم.

کامپوننت Navigation چیست؟

کامپوننت Navigation به مجموعه‌ای از کتابخانه‌ها، یک پلاگین و ابزارهایی گفته می‌شود که موجب سهولت ناوبری در اندروید می‌شوند. کامپوننت Navigation جت‌پک به ما کمک می‌کند که به پیاده‌سازی ناوبری بپردازیم و مواردی از یک کلیک ساده روی دکمه تا الگوهای پیچیده‌تر مانند نوارهای اپلیکیشن و منوی ناوبری را شامل می‌شود. کامپوننت Navigation همچنین موجب ایجاد یک تجربه کاربری منسجم و قابل پیش‌بینی از طریق رعایت یک مجموعه اصول تثبیت شده می‌شود.

کامپوننت Navigation شامل سه بخش کلیدی است.

گراف Navigation

این یک نوع منبع جدید است. در واقع یک فایل XML است که شامل اطلاعات مرتبط با ناوبری دریک مکان متمرکز است. این موارد شامل همه زمینه‌های محتوایی منفرد در اپلیکیشن است که مقصد (Destination) نام دارند و همچنین شامل مسیرهای کمک که کاربر می‌تواند در اپلیکیشن طی کند نیز می‌شود.

کامپوننت Navigation در اندروید

Navgraph مربوط به ادیتور Navigation را می‌توان به صورت تصویر فوق بصری‌سازی کرد. این صفحه‌ها مقصد نام دارند. در واقع چیزی به جز فرگمان نیستند. فلش‌های بین این مقاصد به نام اکشن خوانده می‌شوند. این اکشن‌ها مسیرهایی که کاربر می‌تواند طی کند را تعریف می‌کنند.

این یک کانتینر خالی است که مقاصدی از گراف ناوبری نمایش می‌دهد. کامپوننت Navigation یک پیاده‌سازی پیش‌فرض NavHost از NavHostFragment را شامل می‌شود که مقاصد فرگمان را نمایش می‌دهد.

یک شیء است که ناوبری اپلیکیشن را درون یک NavHost مدیریت می‌کند. NavController به هماهنگ کردن تعویض محتوای مقاصد در NavController در زمان حرکت کاربران به نقاط مختلف اپلیکیشن می‌پردازد.

نکته: گراف ناوبری را می‌توان در ادیتور Navigation جدید که از نسخه 3.3 اندروید استودیو عرضه شده است مشاهده کرد. این قابلیت جالب امکان مشاهده همه ناوبری‌ها را در یک جا فراهم ساخته است.

مزایا

کامپوننت Navigation چند مزیت ارائه می‌کند که شامل موارد زیر هستند:

  • مدیریت تراکنش‌های فرگمان
  • مدیریت پیش‌فرض اکشن‌های up و back به صورت صحیح
  • ارائه منابع استاندارد برای انیمیشن و تراکنش‌ها
  • پیاده‌سازی مدیریت deep linking
  • گنجاندن الگوهای UI ناوبری مانند منوهای ناوبری و ناوبری دکمه‌ای با نیاز به کمترین کار اضافی
  • Safe Args – یک پلاگین Gradle است که ایمنی نوع را در زمان ناوبری و ارسال داده‌ها بین مقاصد مختلف تأمین می‌کند.
  • پشتیبانی از ViewModel – می‌توان دامنه یک ViewModel را به یک گراف ناوبری اختصاص داد تا داده‌های مرتبط با UI بین مقاصد گراف مبادله شوند.

مثال

در این بخش از طریق ایجاد یک مثال ساده به بررسی طرز کار این کامپوننت می‌پردازیم. در ادامه یک Activity ساده با دو فرگمان ایجاد خواهیم کرد و بررسی می‌کنیم که چطور می‌توان از کامپوننت Navigation برای ناوبری بین دو فرگمان استفاده کرد.

گام 1

یک پروژه جدید با پشتیبانی از android بسازید و یا کدبیس موجود را برای پشتیبانی از android ریفکتور کنید. android یک پروژه اوپن سورس است که تیم اندروید از آن برای توسعه، تست، بسته‌بندی، نسخه‌بندی و انتشار کتابخانه‌های درون جت‌پک استفاده می‌کند.

گام 2

وابستگی‌های زیر را به فایل build.gradle اضافه کنید:

1dependencies {
2  def nav_version = "2.3.0-alpha02"
3
4  // Java language implementation
5  implementation "androidx.navigation:navigation-fragment:$nav_version"
6  implementation "androidx.navigation:navigation-ui:$nav_version"
7
8  // Kotlin
9  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
10  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
11
12  // Dynamic Feature Module Support
13  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
14
15  // Testing Navigation
16  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
17}

این‌ها وابستگی‌های مختلفی برای الزامات متفاوت هستند. شما می‌توانید بسته به شرایط خودتان هر کدام از آن‌ها را انتخاب کنید.

گام 3

یک گراف ناوبری ایجاد کنید. برای افزودن یک گراف ناوبری به پروژه باید کارهای زیر را انجام دهید.

  • در پنجره پروژه روی دایرکتوری res راست-کلیک کنید و گزینه New > Android Resource File را انتخاب کنید. بدین ترتیب کادر محاوره‌ای New Resource File ظاهر می‌شود.
  • یک نام مانند nav_graph در فیلد نام فایل وارد کنید.
  • از منوی بازشدنی نوع Resource گزینه Navigation را انتخاب و سپس روی OK کلیک کنید.

کامپوننت Navigation در اندروید

زمانی که نخستین گراف ناوبری را اضافه کردید، اندروید استودیو یک دایرکتوری منبع navigation درون دایرکتوری res اضافه می‌کند. این دایرکتوری شامل فایل منبع گراف ناوبری شما است. فایلی که ایجاد می‌شود به صورت زیر است:

1<?xml version="1.0" encoding="utf-8"?>
2<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3    xmlns:app="http://schemas.android.com/apk/res-auto"
4    android:id="@+id/nav_graph">
5</navigation>

عنصر <navigation> همان عنصر ریشه گراف ناوبری است. زمانی که مقاصد و اکشن‌های اتصال‌دهنده را به گراف خود اضافه کنید، می‌بینید که عناصر متناظر <destination> و <action> به صورت عناصر فرزند اضافه می‌شوند. اگر گراف‌های تو در تو داشته باشید، به صورت عناصر <navigation> فرزند ظاهر می‌شوند.

گام ۴

یک NavHost یک فایل XML اکتیویتی خود اضافه کنید. ساختار فایل nav_host_fragment به صورت زیر است:

  • خصوصیت android:name شامل نام کلاس NavHost است.
  • خصوصیت app:navGraph اقدام به اتصال NavHostFragment با یک گراف ناوبری می‌کند. گراف ناوبری همه مقاصد را در این NavHostFragment ذکر می‌کند تا مشخص شود که کدام کاربران می‌توانند ناوبری کنند.
  • خصوصیت ”app:defaultNavHost=”true این اطمینان را ایجاد می‌کند که NavHostFragment دکمه Back سیستم را تفسیر می‌کند. توجه کنید که تنها یک NavHost می‌تواند به صورت پیش‌فرض باشد. اگر چندین میزان در همان لی‌آوت داشته باشید، باید مطمئن شوید که تنها یک NavHost پیش‌فرض مورد اشاره قرار گرفته است.

گام ۵

وابستگی‌ها و مسیرها را در nav_graph اضافه کنید. پیش از افزودن وابستگی‌ها باید دو فرگمان و XML آن‌ها را ایجاد کنید.

کامپوننت Navigation در اندروید

اکنون فایل XML را برای کلاس FragmentOne بسازید.

به طور مشابه فرگمان دوم را ایجاد کنید. سپس مقاصد را به nav_graph اضافه می‌کنیم. ساختار یک مقصد به صورت زیر است:

  • فیلد Type مشخص می‌سازد که مقصد به صوت یک فرگمان، اکتیویتی یا کلاس سفارشی دیگر در کد منبع پیاده‌سازی شده است.
  • فیلد Label شامل نام فایل لی‌آوت XML مقصد است.
  • فیلد ID شامل شناسه مقصد است که برای اشاره به مقصد در کد استفاده می‌شود.
  • منوی بازشدنی Class نام کلاسی که با مقصد مرتبط است را نمایش می‌دهد. می‌توانید روی این منوی بازشدنی کلیک کنید تا کلاس مرتبط را به نوع مقصد دیگری انتساب دهید.

از ادیتور ناوبری، نمای ما به صورت زیر دیده می‌شود:

کامپوننت Navigation در اندروید

اکنون NavHost را به فایل XML مربوط به activity_main اضافه می‌کنیم:

1<androidx.constraintlayout.widget.ConstraintLayout
2    xmlns:android="http://schemas.android.com/apk/res/android"
3    xmlns:app="http://schemas.android.com/apk/res-auto"
4    xmlns:tools="http://schemas.android.com/tools"
5    android:layout_width="match_parent"
6    android:layout_height="match_parent"
7    tools:context=".MainActivity">
8
9
10
11    <fragment
12        android:id="@+id/nav_host_fragment"
13        android:name="androidx.navigation.fragment.NavHostFragment"
14        android:layout_width="match_parent"
15        android:layout_height="match_parent"
16        app:defaultNavHost="true"
17        app:navGraph="@navigation/nav_graph" />
18
19</androidx.constraintlayout.widget.ConstraintLayout>

اکنون MainActivity  به صورت زیر در آمده است:

1package com.example.navigationsample
2import android.support.v7.app.AppCompatActivity
3import android.os.Bundle
4import androidx.navigation.findNavController
5class MainActivity : AppCompatActivity() {
6    override fun onCreate(savedInstanceState: Bundle?) {
7        super.onCreate(savedInstanceState)
8        setContentView(R.layout.activity_main)
9    }
10    override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()
11}

بدین ترتیب کار در این بخش پایان می‌پذیرد. با کلیک روی دکمه run می‌توانید به بررسی عملی کامپوننت بپردازید:

کامپوننت Navigation در اندروید

در پشت این عملکرد خارق‌العاده تنها یک خط کد در فرگمان یک قرار دارد که روی کلیک دکمه عمل می‌کند. از این رو نیازی به تراکنش‌های فرگمان نیست.

1btn_next.setOnClickListener {
2   view.findNavController().navigate(R.id.action_fragment1_to_fragment2)
3}

باید NavController را پیدا کرده و به ID اکشن که در XML مشخص شده به آن بدهیم.

بررسی نکات دیگر

برای هر اکشن ناوبری یک مقصد به back stack اضافه می‌شود. در پیاده‌سازی پیشین زمانی که از یک فرگمان به فرگمان دیگر می‌رفتیم، اگر روی دکمه بازگشت در فرگمان دوم کلیک می‌کردیم، به فرگمان اول بازمی‌گشتیم.

اما فرض کنید در مورد یک فرگمان اسپلش این رفتار قابل اجرا نیست. در این حالت باید خصوصیت‌های دیگری به اکشن در nav_graph اضافه کنیم و یا گزینه‌ای برای افزودن این مشخصه‌ها به صورت برنامه‌نویسی شده با استفاده از NavOptions داشته باشیم. NavOptions گزینه‌های خاصی برای اکشن‌های ناوبری ذخیره می‌کند.

در ادامه به بررسی مشخصه‌ها در XML می‌پردازیم:

کامپوننت Navigation در اندروید

در مورد مشکل اسپلش که اشاره کردیم باید دو مشخصه متفاوت داشته باشیم:

1app:popUpTo="@id/fragmentOne"
2app:popUpToInclusive="true"

با افزودن این مشخصه‌ها به nav_graph، فایل به صورت زیر ویرایش می‌شود:

1<navigation xmlns:android="http://schemas.android.com/apk/res/android"
2    xmlns:app="http://schemas.android.com/apk/res-auto"
3    xmlns:tools="http://schemas.android.com/tools"
4    android:id="@+id/nav_graph"
5    app:startDestination="@id/fragmentOne">
6
7    <fragment
8        android:id="@+id/fragmentOne"
9        android:name="com.example.navcomponent.FragmentOne"
10        android:label="Fragment1"
11        tools:layout="@layout/fragment_one" >
12        <action
13            android:id="@+id/action_fragment1_to_fragment2"
14            app:destination="@id/fragmentTwo"
15            app:popUpTo="@id/fragmentOne"
16            app:popUpToInclusive="true"
17            />
18    </fragment>
19
20    <fragment
21        android:id="@+id/fragmentTwo"
22        android:name="com.example.navcomponent.FragmentTwo"
23        android:label="Fragment2"
24        tools:layout="@layout/fragment_two">
25    </fragment>
26
27</navigation>

نتیجه کار به صورت زیر است:

کامپوننت Navigation در اندروید

ساختار یک اکشن

1<action android:id="@+id/next_action"
2            app:destination="@+id/flow_step_one"
3            app:enterAnim="@anim/slide_in_right"
4            app:exitAnim="@anim/slide_out_left"
5            app:popEnterAnim="@anim/slide_in_left"
6            app:popExitAnim="@anim/slide_out_right"
7            app:popUpTo="@id/fragmentOne"
8            app:popUpToInclusive="true" />
  • فیلد id شامل ID اکشنی است که از سوی NavHost برای ناوبری استفاده می‌شود.
  • چهار نوع انیمیشن وجود دارند که شامل app:enterAnim, app:exitAnim, app:popEnterAnim و app:popExitAnim هستند. این انیمیشن‌ها را می‌توان در زمان اضافه کردن اولیه فرگمان و حذف آن می‌توان تعیین کرد. همین کار را در زمان pop کردن از فرگمان‌های دیگر می‌توان انجام داد.
  • خصوصیت app:popUpTo برای نشانه‌گذاری اکشن poping فرگمان جاری تا زمانی که دوباره از اکشن جاری pop شود می‌توان استفاده کرد. به این ترتیب همه مقاصد غیر منطبق از back stack حذف می‌شوند تا این که مقصد پیدا شود.
  • app:popUpToInclusive برای تعیین این که گزینه poping باید شامل وهله جاری باشد یا نه استفاده می‌شود.
  • برای این استفاده می‌شود که آیا اکشن ناوبری باید به صورت single-top اجرا شود یا نه. طرز کار این تابع شبیه شیوه عمل android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP روی اکتیویتی‌ها است.

نکته: اگر از app:popUpToInclusive در زمان ناوبری مکرر بین فرگمان‌ها استفاده نمی‌کنید، back stack ممکن است شامل دو یا چند وهله از مقصد خاص باشد.

برای دستیابی برنامه‌نویسی شده به رفتار خاص صفحه اسپلش که اشاره شد، می‌توان از سازنده NavOptions استفاده کرد. در زمان کلیک روی دکمه باید NavController را تعیین کنیم:

1btn_next.setOnClickListener {
2view.findNavController().navigate(R.id.action_fragment1_to_fragment2,null, NavOptions.Builder()
3            .setPopUpTo(R.id.fragmentOne, true)
4            .build())
5}

ارسال آرگومان‌ها بین فرگمان‌ها

در زمان ناوبری بین فرگمان‌ها معمول است که داده‌ها را بین آن‌ها به اشتراک بگذاریم. یکی از ساده‌ترین روش‌ها برای انجام این کار استفاده از یک ViewModel مشترک است. روش دیگر به صورت ارسال آرگومان‌ها و خواندن آن‌ها در مقصد است. از آنجا که از کامپوننت Navigation استفاده می‌کنیم، باید بررسی کنیم که چگونه می‌توانیم داده‌ها را با استفاده از پلاگین safe args بین فرگمان‌ها به اشتراک بگذاریم.

Safe Args

پلاگین Safe Args کدی تولید می‌کند که به ما امکان می‌دهد تا ناوبری با امنیت نوع و ارسال آرگومان را اجرا کنیم. Safe Args این فرصت را برای ما فراهم می‌سازد که از شر کدهایی که در زمان ارسال مقادیر بین مقاصد ارسال می‌کنیم آزاد شویم.

قبل از هر چیز باید Safe Args را به پروژه اضافه کنیم. به این منظور classpath را در فایل build.gradle سطح بالا بگنجانید:

1buildscript {
2    repositories {
3        google()
4    }
5    dependencies {
6        def nav_version = "2.3.0-alpha01"
7        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
8    }
9}

برای تولید کد زبان جاوا که مناسب ماژول‌های جاوا یا ماژول‌های ترکیبی جاوا و کاتلین باشد، باید این خط را به فایل در سطح app یا module اضافه کنید:

1apply plugin: "androidx.navigation.safeargs"

به طور جایگزین برای تولید کد کاتلین که ماژول‌های صرفاً کاتلین مناسب باشد، باید خط زیر را اضافه کنید:

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

پس از افزودن خط فوق، فایل build.gradle سطح بالا باید به صورت زیر درآید:

1apply plugin: 'com.android.application'
2
3apply plugin: 'kotlin-android'
4
5apply plugin: 'kotlin-android-extensions'
6
7apply plugin: "androidx.navigation.safeargs.kotlin" // add this

پس از این که Safe Args را فعال کردید، این پلاگین کدی تولید می‌کند که شامل کلاس‌ها و متدهایی برای هر اکشن تعریف شده است. برای هر اکشن Safe Args یک کلاس نیز برای مقصد اصلی تولید می‌کند منظور از مقصد اصلی (originating destination) مقصدی است که اکشن از آنجا منشأ می‌گیرد. نام کلاس تولید شده ترکیبی از کلاس مقصد اصلی و کلمه Directions است. برای نمونه اگر مقصد دارای نامی مانند FragmentOne باشد، کلاس تولید شده دارای نامی مانند FragmentOneDirections خواهد بود.

کلاس تولید شده شامل یک متد استاتیک برای هر اکشن تعریف شده در کلاس اصلی است. این متد همه پارامترهای اکشن تعریف شده را به عنوان آرگومان می‌گیرد و یک شیء NavDirections بازگشت می‌دهد که می‌توانید به ()navigate ارسال کنید. کد تولید شده را می‌توانید در پوشه تولید شده بیابید:

کامپوننت Navigation در اندروید

به عنوان مثال، فرض کنید می‌خواهیم یک گراف ناوبری با یک اکشن منفرد داشته باشیم که مقصد اصلی یعنی FragmentOne را به مقصد دریافتی یعنی FragmentTwo اتصال می‌دهد.

Safe Args یک کلاس FragmentOneDirections با یک متد منفرد تولید می‌کند، ()actionFragmentOneToFragmentTwo نیز یک شیء NavDirections بازگشت می‌دهد. در ادامه این شیء NavDirections بازگشتی چنان که در مثال زیر می‌بینید، می‌تواند مستقیماً به ()navigate ارسال شود:

1<?xml version="1.0" encoding="utf-8"?>
2<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3    xmlns:app="http://schemas.android.com/apk/res-auto"
4    xmlns:tools="http://schemas.android.com/tools"
5    android:id="@+id/nav_graph"
6    app:startDestination="@id/fragmentOne">
7
8    <fragment
9        android:id="@+id/fragmentOne"
10        android:name="com.example.navcomponent.FragmentOne"
11        android:label="Fragment1"
12        tools:layout="@layout/fragment_one" >
13        <action
14            android:id="@+id/action_fragment1_to_fragment2"
15            app:destination="@id/fragmentTwo"
16            app:popUpTo="@id/fragmentOne"
17            app:popUpToInclusive="true"
18            />
19    </fragment>
20
21    <fragment
22        android:id="@+id/fragmentTwo"
23        android:name="com.example.navcomponent.FragmentTwo"
24        android:label="Fragment2"
25        tools:layout="@layout/fragment_two">
26        <argument
27            android:name="name"
28            android:defaultValue="Hello"
29            app:argType="string" />
30    </fragment>
31
32</navigation>

در FragmentOne آرگومان‌هایی ایجاد می‌شود که با استفاده از شیء Directories ارسال می‌شود:

1btn_next.setOnClickListener {
2    val action = FragmentOneDirections.actionFragment1ToFragment2("Android")
3    view.findNavController().navigate(action)
4}

در فرگمان دوم args را دریافت می‌کند:

1override fun onActivityCreated(savedInstanceState: Bundle?) {
2    super.onActivityCreated(savedInstanceState)
3    val args: FragmentTwoArgs by navArgs()
4    args.let {
5        Toast.makeText(activity!!,it.name,Toast.LENGTH_SHORT).show()
6    }
7}

کد تولید شده را می‌توانید در پوشه builder بیابید:

کامپوننت Navigation در اندروید

انواع پشتیبانی شده آرگومان

در تصویر زیر انواع مختلف از آرگومان‌ها که پشتیبانی می‌شوند را مشاهده می‌کنید:

کامپوننت Navigation در اندروید

سخن پایانی

بدین ترتیب با مطالعه این مقاله باید هم اینک دانشی مقدماتی در مورد پیاده‌سازی کامپوننت داشته باشید و در ادامه تلاش کنید کدهای قدیمی که برای تراکنش‌های فرگمان استفاده می‌کردید را حذف کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
۲ دیدگاه برای «کامپوننت Navigation در اندروید — از صفر تا صد»

سلام داش میثم
حاجی دمت گرم کامل و جامع ان شا الله برای jet pack هم یه مطلب بدین

ببخشید من روی ایتم recycervliew تایین کرده ام و زمانی که کلیک می کنم و به استک اضافه می کنم یک ایتم رو و مجددان بر گرده ام دوباره به recyclerview کلا ریستارت میشه یعنی داده مجددان از سمت سرور دریافت میشه این در صورتی که در ورژن قدیم به سادگی بدون اینکه مشکلی پیش بیاد عمل می کرد و فقط backstack میشد و state کاربر هم ذخیره میشد فرضا کاربر در ایتم 50 بود بر می گشت دقیقا توی همون state باقی میموند.به نظر شما مشکل از کجاست.

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *