نقل البيانات إلى مكوّن التنقّل

مكوِّن التنقل هو مكتبة يمكنها إدارة التنقل المُعقَّد، والصور المتحركة للانتقال، والربط بصفحة معيّنة، والوسيطة التي تم فحصها في وقت التجميع والتي يتم تمريرها بين الشاشات في تطبيقك.

يعمل هذا المستند كدليل للأغراض العامة لنقل تطبيق موجود لاستخدام مكون التنقل.

بصورة عامة، تتضمّن عملية نقل البيانات الخطوات التالية:

  1. نقل منطق واجهة المستخدم الخاصة بالشاشة إلى خارج الأنشطة - يمكنك إيقاف الأنشطة في واجهة المستخدم الخاصة بالتطبيق، مع التأكّد من أنّ كل نشاط لا يملك إلا منطق مكونات واجهة المستخدم العامة للتنقل، مثل Toolbar، مع تفويض تنفيذ كل شاشة إلى جزء أو وجهة مخصصة.

  2. دمج مكوِّن التنقل - لكل نشاط، أنشئ رسمًا بيانيًا للتنقل يحتوي على جزء واحد أو أكثر تتم إدارته من خلال هذا النشاط. استبدِل المعاملات المجزأة بعمليات مكوّن التنقل.

  3. إضافة وجهات النشاط - استبدِل استدعاءات startActivity() بإجراءات تستخدم وجهات الأنشطة.

  4. دمج الأنشطة - لدمج الرسوم البيانية للتنقل في الحالات التي تشترك فيها أنشطة متعددة في تخطيط مشترك.

المتطلّبات الأساسية

يفترض هذا الدليل أنّك قد نقلت تطبيقك من قبل لاستخدام مكتبات AndroidX. في حال عدم إجراء ذلك، يمكنك نقل مشروعك لاستخدام AndroidX قبل المتابعة.

نقل منطق واجهة المستخدم الخاصة بالشاشة خارج الأنشطة

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

في سياق تطبيقك، يجب أن تعمل الأنشطة كمضيف للتنقل ويجب أن تتضمن المنطق والمعرفة حول كيفية الانتقال بين الشاشات وتمرير البيانات، وما إلى ذلك. ومع ذلك، من الأفضل إدارة تفاصيل واجهة المستخدم الخاصة بك في جزء أصغر قابل لإعادة الاستخدام من واجهة المستخدم الخاصة بك. التنفيذ المقترَح لهذا النمط هو الأجزاء. راجِع النشاط الأحادي: لماذا ومتى وكيف لمعرفة المزيد عن مزايا استخدام الأجزاء. يتيح التنقّل استخدام الأجزاء عبر تبعية جزء التنقّل. يدعم التنقل أيضًا أنواع الوجهات المخصّصة.

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

نقدّم لك ميزة الأجزاء

لتوضيح عملية تقديم الأجزاء، لنبدأ بمثال على تطبيق يتضمّن شاشتَين: شاشة قائمة المنتجات وشاشة تفاصيل المنتج. يؤدي النقر فوق منتج ما في شاشة القائمة إلى نقل المستخدم إلى شاشة التفاصيل لمعرفة المزيد عن المنتج.

في هذا المثال، تعد شاشات القائمة والتفاصيل أنشطة منفصلة حاليًا.

إنشاء تنسيق جديد لاستضافة واجهة المستخدم

لإضافة جزء، ابدأ بإنشاء ملف تنسيق جديد للنشاط لاستضافة الجزء. يحل ذلك محل تنسيق عرض المحتوى الحالي للنشاط.

لعرض بسيط، يمكنك استخدام FrameLayout، كما هو موضّح في مثال product_list_host التالي:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/main_content"
   android:layout_height="match_parent"
   android:layout_width="match_parent" />

تشير السمة id إلى قسم المحتوى الذي نضيف إليه الجزء لاحقًا.

بعد ذلك، في دالة onCreate() الخاصة بنشاطك، عليك تعديل مرجع ملف التنسيق في دالة onCreate لنشاطك التجاري للإشارة إلى ملف التنسيق الجديد هذا:

Kotlin

class ProductListActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Replace setContentView(R.layout.product_list) with the line below
        setContentView(R.layout.product_list_host)
        ...
    }
}

Java

public class ProductListActivity extends AppCompatActivity {
    ...
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // Replace setContentView(R.layout.product_list); with the line below
        setContentView(R.layout.product_list_host);
        ...
    }
}

يُستخدم التنسيق الحالي (product_list، في هذا المثال) كطريقة العرض الجذر للجزء الذي تريد إنشاءه.

إنشاء جزء

يمكنك إنشاء جزء جديد لإدارة واجهة المستخدم لشاشتك. من الممارسات الجيدة أن تكون متناسقًا مع اسم مضيف النشاط. يستخدم المقتطف أدناه السمة ProductListFragment، على سبيل المثال:

Kotlin

class ProductListFragment : Fragment() {
    // Leave empty for now.
}

Java

public class ProductListFragment extends Fragment {
    // Leave empty for now.
}

نقل منطق النشاط إلى جزء

مع تحديد الجزء، تتمثل الخطوة التالية في نقل منطق واجهة المستخدم لهذه الشاشة من النشاط إلى هذا الجزء الجديد. إذا كنت تأتي من بنية قائمة على النشاط، فمن المحتمل أن يكون لديك الكثير من منطق إنشاء طرق العرض التي يحدث في دالة onCreate() لنشاطك.

في ما يلي مثال لشاشة تستند إلى النشاط ومنطق واجهة المستخدم التي نحتاج إلى نقلها:

Kotlin

class ProductListActivity : AppCompatActivity() {

    // Views and/or ViewDataBinding references, Adapters...
    private lateinit var productAdapter: ProductAdapter
    private lateinit var binding: ProductListActivityBinding

    ...

    // ViewModels, System Services, other Dependencies...
    private val viewModel: ProductListViewModel by viewModels()

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity)

        // Post view initialization logic
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }
   ...
}

Java

public class ProductListActivity extends AppCompatActivity {

    // Views and/or ViewDataBinding references, adapters...
    private ProductAdapter productAdapter;
    private ProductListActivityBinding binding;

    ...

    // ViewModels, system services, other dependencies...
    private ProductListViewModel viewModel;

    ...

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity);

        // Post view initialization logic
        // Connect adapters
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(v -> { ... });

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts ->
            ...
       );

       // ...and so on
   }

قد يتحكم نشاطك أيضًا في وقت وكيفية انتقال المستخدم إلى الشاشة التالية، كما هو موضح في المثال التالي:

Kotlin

    // Provided to ProductAdapter in ProductListActivity snippet.
    private val productClickCallback = ProductClickCallback { product ->
        show(product)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }

Java

// Provided to ProductAdapter in ProductListActivity snippet.
private ProductClickCallback productClickCallback = this::show;

private void show(Product product) {
    Intent intent = new Intent(this, ProductActivity.class);
    intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
    startActivity(intent);
}

داخل الجزء، يمكنك توزيع هذا العمل بين onCreateView() وonViewCreated()، مع إبقاء منطق التنقّل فقط في النشاط:

Kotlin

class ProductListFragment : Fragment() {

    private lateinit var binding: ProductListFragmentBinding
    private val viewModel: ProductListViewModel by viewModels()

     // View initialization logic
    override fun onCreateView(inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list,
                container,
                false
        )
        return binding.root
    }

    // Post view initialization logic
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }

    // Provided to ProductAdapter
    private val productClickCallback = ProductClickCallback { product ->
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            (requireActivity() as ProductListActivity).show(product)
        }
    }
    ...
}

Java

public class ProductListFragment extends Fragment {

    private ProductAdapter productAdapter;
    private ProductListFragmentBinding binding;

    // View initialization logic
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list_fragment,
                container,
                false);
        return binding.getRoot();
    }

    // Post view initialization logic
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {

        // Connect adapters
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this)
                .get(ProductListViewModel.class);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(...)

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts -> {
            ...
       });

       // ...and so on

    // Provided to ProductAdapter
    private ProductClickCallback productClickCallback = new ProductClickCallback() {
        @Override
        public void onClick(Product product) {
            if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                ((ProductListActivity) requireActivity()).show(product);
            }
        }
    };
    ...
}

في ProductListFragment، لاحظ أنّه ما مِن دعوة إلى setContentView() تضخيم التنسيق وربطه. في أحد الأجزاء، يؤدي onCreateView() إلى إعداد طريقة العرض الجذر تأخذ onCreateView() مثيلاً من LayoutInflater الذي يمكن استخدامه لتكبير طريقة العرض الجذر استنادًا إلى ملف موارد تنسيق. يُعيد هذا المثال استخدام تنسيق product_list الحالي الذي استخدمه النشاط لأنّه لا يلزم إجراء أي تغيير على التنسيق نفسه.

إذا كانت هناك أيّ منطق واجهة مستخدم مخزَّنة في وظائف onStart() أو onResume() أو onPause() أو onStop() الخاصة بنشاطك ولا تتعلق بالتنقّل، يمكنك نقلها إلى الدوال المقابلة التي تحمل الاسم نفسه على الجزء.

تهيئة الجزء في النشاط المضيف

بمجرد نقل منطق كل واجهة المستخدم إلى الجزء، ينبغي أن يظل منطق التنقل فقط في النشاط.

Kotlin

class ProductListActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_list_host)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }
}

Java

public class ProductListActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.product_list_host);
    }

    public void show(Product product) {
        Intent intent = new Intent(this, ProductActivity.class);
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
        startActivity(intent);
    }
}

الخطوة الأخيرة هي إنشاء مثيل للجزء في onCreate() بعد ضبط طريقة عرض المحتوى مباشرةً:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.product_list_host)

    if (savedInstanceState == null) {
        val fragment = ProductListFragment()
        supportFragmentManager
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit()
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.product_list_host);

    if (savedInstanceState == null) {
        ProductListFragment fragment = new ProductListFragment();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit();
    }
}

كما هو موضّح في هذا المثال، تحفظ السمة FragmentManager الأجزاء وتستعيدها تلقائيًا بعد تغييرات الإعدادات، لذا ما عليك سوى إضافة الجزء إذا كانت savedInstanceState فارغة.

تمرير عناصر intent الإضافية إلى الجزء

إذا كان نشاطك يتلقّى دالة Extras من خلال هدف، يمكنك تمريرها إلى الكسر مباشرةً كوسيطات.

في هذا المثال، تتلقى السمة ProductDetailsFragment وسيطاتها مباشرةً من الإضافات الأساسية للنشاط:

Kotlin

...

if (savedInstanceState == null) {
    val fragment = ProductDetailsFragment()

    // Intent extras and Fragment Args are both of type android.os.Bundle.
    fragment.arguments = intent.extras

    supportFragmentManager
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit()
}

...

Java

...

if (savedInstanceState == null) {
    ProductDetailsFragment fragment = new ProductDetailsFragment();

    // Intent extras and fragment Args are both of type android.os.Bundle.
    fragment.setArguments(getIntent().getExtras());

    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit();
}

...

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

دمج مكوِّن التنقّل

بعد استخدام بنية قائمة على الأجزاء، تكون جاهزًا لبدء دمج مكون التنقل.

أولاً، أضف أحدث تبعيات التنقل إلى مشروعك، باتباع التعليمات الواردة في ملاحظات إصدار مكتبة التنقل.

إنشاء رسم بياني للتنقل

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

لإنشاء رسم بياني للتنقّل، ابدأ بإنشاء مجلد موارد جديد باسم navigation. لإضافة الرسم البياني، انقر بزر الماوس الأيمن على هذا الدليل، واختر جديد > ملف موارد التنقل.

يستخدم مكوِّن التنقل نشاطًا باعتباره مضيف للتنقل ويستبدل الأجزاء الفردية في هذا المضيف أثناء تنقل المستخدمين عبر تطبيقك. قبل أن تتمكن من البدء في تخطيط تنقل تطبيقك مرئيًا، تحتاج إلى ضبط NavHost داخل النشاط الذي سيستضيف هذا الرسم البياني. نظرًا لأننا نستخدم الأجزاء، يمكننا استخدام تنفيذ NavHost التلقائي NavHostFragment في مكوِّن التنقل.

يتم ضبط NavHostFragment من خلال عنصر FragmentContainerView يتم وضعه داخل نشاط مضيف، كما هو موضّح في المثال التالي:

<androidx.fragment.app.FragmentContainerView
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:id="@+id/main_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

تشير السمة app:NavGraph إلى الرسم البياني للتنقّل المرتبط بمضيف التنقّل هذا. يؤدي ضبط هذه الخاصية إلى تضخيم الرسم البياني للتنقل وتعيين خاصية الرسم البياني على NavHostFragment. تضمن السمة app:defaultNavHost أنّ جهاز NavHostFragment يعترض زر الرجوع في النظام.

إذا كنت تستخدم التنقّل بمستوى أعلى مثل DrawerLayout أو BottomNavigationView، سيحلّ هذا FragmentContainerView محل عنصر عرض المحتوى الرئيسي. راجع تحديث مكونات واجهة المستخدم باستخدام NavigationUI للحصول على أمثلة.

للحصول على تنسيق بسيط، يمكنك تضمين عنصر FragmentContainerView هذا كعنصر ثانوي للجذر ViewGroup:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_height="match_parent"
   android:layout_width="match_parent">

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/main_content"
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

</FrameLayout>

إذا نقرت على علامة التبويب Design (التصميم) في الأسفل، من المفترض أن ترى رسمًا بيانيًا مشابهًا للرسم الموضح أدناه. في أعلى يمين الرسم البياني، ضمن الوجهات، يمكنك الاطّلاع على مرجع إلى نشاط NavHost في شكل layout_name (resource_id).

انقر على زرّ الإضافة بالقرب من أعلى الصفحة لإضافة الأجزاء إلى هذا الرسم البياني.

يشير عنصر التنقل إلى الشاشات الفردية باسم الوجهات. يمكن أن تكون الوجهات أجزاءً أو أنشطة أو وجهات مخصّصة. يمكنك إضافة أي نوع من الوجهات إلى الرسم البياني، ولكن تجدر الإشارة إلى أنّ وجهات الأنشطة تُعتبر وجهات نهائية، لأنّه بعد الانتقال إلى وجهة نشاط، تعمل ضمن مضيف تنقّل ورسم بياني منفصلَين.

يشير مكوِّن التنقل إلى الطريقة التي ينتقل بها المستخدمون من وجهة إلى أخرى باعتبارها إجراءات. يمكن أن تصف الإجراءات أيضًا الرسوم المتحركة الانتقالية وسلوك البوب.

إزالة المعاملات المجزأة

الآن وبعد استخدام مكوِّن التنقّل، إذا كنت تتنقل بين الشاشات المستندة إلى الأجزاء ضمن النشاط نفسه، يمكنك إزالة تفاعلات FragmentManager.

إذا كان تطبيقك يستخدم أجزاءً متعددة ضمن النشاط نفسه أو عناصر التنقّل على المستوى الأعلى، مثل تنسيق اللائحة أو شريط التنقّل السفلي، من المحتمل أنك تستخدم FragmentManager وFragmentTransactions لإضافة أجزاء أو استبدالها في قسم المحتوى الرئيسي في واجهة المستخدم. ويمكن الآن استبدال هذا الجزء وتبسيطه باستخدام مكوِّن التنقل من خلال توفير إجراءات لربط الوجهات ضمن الرسم البياني ثم التنقل باستخدام NavController.

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

نشاط واحد يدير أجزاء متعددة

إذا كان لديك نشاط واحد يدير أجزاء متعدّدة، قد يبدو رمز نشاطك على النحو التالي:

Kotlin

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Logic to load the starting destination
        //  when the Activity is first created
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit()
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    fun navigateToProductDetail(productId: String) {
        val fragment = new ProductDetailsFragment()
        val args = Bundle().apply {
            putInt(KEY_PRODUCT_ID, productId)
        }
        fragment.arguments = args

        supportFragmentManager.beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit()
    }
}

Java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Logic to load the starting destination when the activity is first created.
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit();
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    public void navigateToProductDetail(String productId) {
        Fragment fragment = new ProductDetailsFragment();
        Bundle args = new Bundle();
        args.putInt(KEY_PRODUCT_ID, productId);
        fragment.setArguments(args);

        getSupportFragmentManager().beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit();
    }
}

داخل وجهة المصدر، قد تستدعي دالة تنقل كرد على حدث ما، كما هو موضح أدناه:

Kotlin

class ProductListFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked
        //  in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
            (requireActivity() as MainActivity).navigateToProductDetail(product.id)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
    // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> (
        ((MainActivity) requireActivity()).navigateToProductDetail(product.getId())
    );
}

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

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_detail" />
    </fragment>
    <fragment
        android:id="@+id/product_detail"
        android:name="com.example.android.persistence.ui.ProductDetailFragment"
        android:label="Product Detail"
        tools:layout="@layout/product_detail">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

بعد ذلك، يمكنك تحديث نشاطك:

Kotlin

class MainActivity : AppCompatActivity() {

    // No need to load the start destination, handled automatically by the Navigation component
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    // No need to load the start destination, handled automatically by the Navigation component
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

لم يعُد النشاط بحاجة إلى طريقة navigateToProductDetail(). في القسم التالي، نعدّل السمة ProductListFragment لاستخدام NavController للانتقال إلى شاشة تفاصيل المنتج التالية.

تمرير الوسيطات بأمان

يحتوي مكوِّن التنقل على مكوّن Gradle الإضافي يسمى Safe Args، وهو ينشئ فئات بسيطة للكائنات والمنشئين من أجل الوصول الآمن من النوع إلى الوسيطات المحددة للوجهات والإجراءات.

بعد تطبيق المكوِّن الإضافي، تؤدي أي وسيطات معرّفة في وجهة في الرسم البياني للتنقّل إلى إنشاء إطار عمل مكوِّن التنقل لفئة Arguments توفر وسيطات من النوع الآمن للوجهة المستهدفة. يؤدي تحديد إجراء إلى إنشاء المكوّن الإضافي لفئة ضبط Directions يمكن استخدامها لإخبار NavController بكيفية انتقال المستخدم إلى الوجهة المستهدفة. عندما يشير إجراء إلى وجهة تتطلّب وسيطات، تتضمّن فئة Directions التي تم إنشاؤها طرق الدالة الإنشائية التي تتطلب هذه المعلَمات.

داخل الجزء، استخدِم NavController وفئة Directions التي تم إنشاؤها لتوفير وسيطات آمنة للنوع في الوجهة المستهدفة، كما هو موضّح في المثال التالي:

Kotlin

class ProductListFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the NavController to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
        val directions = ProductListDirections.navigateToProductDetail(product.id)
        findNavController().navigate(directions)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> {
        ProductListDirections.ViewProductDetails directions =
                ProductListDirections.navigateToProductDetail(product.getId());
        NavHostFragment.findNavController(this).navigate(directions);
    };
}

التنقل على المستوى الأعلى

إذا كان تطبيقك يستخدم DrawerLayout، قد يتوفّر لديك الكثير من أساليب الضبط في نشاطك التي تدير فتح اللائحة وإغلاقها والانتقال إلى وجهات أخرى.

قد يبدو النشاط الناتج كما يلي:

Kotlin

class MainActivity : AppCompatActivity(),
    NavigationView.OnNavigationItemSelectedListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val toggle = ActionBarDrawerToggle(
                this,
                drawerLayout,
                toolbar,
                R.string.navigation_drawer_open, 
                R.string.navigation_drawer_close
        )
        drawerLayout.addDrawerListener(toggle)
        toggle.syncState()

        navView.setNavigationItemSelectedListener(this)
    }

    override fun onBackPressed() {
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.closeDrawer(GravityCompat.START)
        } else {
            super.onBackPressed()
        }
    }

    override fun onNavigationItemSelected(item: MenuItem): Boolean {
        // Handle navigation view item clicks here.
        when (item.itemId) {
            R.id.home -> {
                val homeFragment = HomeFragment()
                show(homeFragment)
            }
            R.id.gallery -> {
                val galleryFragment = GalleryFragment()
                show(galleryFragment)
            }
            R.id.slide_show -> {
                val slideShowFragment = SlideShowFragment()
                show(slideShowFragment)
            }
            R.id.tools -> {
                val toolsFragment = ToolsFragment()
                show(toolsFragment)
            }
        }
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        drawerLayout.closeDrawer(GravityCompat.START)
        return true
    }
}

private fun show(fragment: Fragment) {

    val drawerLayout = drawer_layout as DrawerLayout
    val fragmentManager = supportFragmentManager

    fragmentManager
            .beginTransaction()
            .replace(R.id.main_content, fragment)
            .commit()

    drawerLayout.closeDrawer(GravityCompat.START)
}

Java

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this,
                drawer,
                toolbar,
                R.string.navigation_drawer_open,
                R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        navigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.home) {
            Fragment homeFragment = new HomeFragment();
            show(homeFragment);
        } else if (id == R.id.gallery) {
            Fragment galleryFragment = new GalleryFragment();
            show(galleryFragment);
        } else if (id == R.id.slide_show) {
            Fragment slideShowFragment = new SlideShowFragment();
            show(slideShowFragment);
        } else if (id == R.id.tools) {
            Fragment toolsFragment = new ToolsFragment();
            show(toolsFragment);
        }

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void show(Fragment fragment) {

        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
        FragmentManager fragmentManager = getSupportFragmentManager();

        fragmentManager
                .beginTransaction()
                .replace(R.id.main_content, fragment)
                .commit();

        drawerLayout.closeDrawer(GravityCompat.START);
    }
}

بعد إضافة مكوِّن التنقل إلى مشروعك وإنشاء رسم بياني للتنقل، أضف كل وجهات المحتوى من الرسم البياني (مثل الصفحة الرئيسية والمعرض وSlideShow والأدوات من المثال أعلاه). تأكَّد من أنّ قيم عنصر القائمة id تتطابق مع قيم id للوجهة المرتبطة بها، كما هو موضّح أدناه:

<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/home"
            android:icon="@drawable/ic_menu_camera"
            android:title="@string/menu_home" />
        <item
            android:id="@+id/gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/menu_gallery" />
        <item
            android:id="@+id/slide_show"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/menu_slideshow" />
        <item
            android:id="@+id/tools"
            android:icon="@drawable/ic_menu_manage"
            android:title="@string/menu_tools" />
    </group>
</menu>
<!-- activity_main_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_graph"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.example.HomeFragment"
        android:label="Home"
        tools:layout="@layout/home" />

    <fragment
        android:id="@+id/gallery"
        android:name="com.example.GalleryFragment"
        android:label="Gallery"
        tools:layout="@layout/gallery" />

    <fragment
        android:id="@+id/slide_show"
        android:name="com.example.SlideShowFragment"
        android:label="Slide Show"
        tools:layout="@layout/slide_show" />

    <fragment
        android:id="@+id/tools"
        android:name="com.example.ToolsFragment"
        android:label="Tools"
        tools:layout="@layout/tools" />

</navigation>

إذا طابقت قيم id من القائمة والرسم البياني، يمكنك اختيار نقل البيانات من NavController إلى هذا النشاط من أجل معالجة التنقّل تلقائيًا استنادًا إلى عنصر في القائمة. تعالج NavController أيضًا فتح وإغلاق DrawerLayout والتعامل مع سلوك زر "السهم المتّجه للأعلى" و"الرجوع" بشكل مناسب.

يمكن بعد ذلك تحديث "MainActivity" لتوصيل NavController إلى Toolbar وNavigationView.

للاطّلاع على مثال، يمكنك الاطّلاع على المقتطف التالي:

Kotlin

class MainActivity : AppCompatActivity()  {

    val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) }
    val navController by lazy {
      (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController
    }
    val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)

        // Show and Manage the Drawer and Back Icon
        setupActionBarWithNavController(navController, drawerLayout)

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        navigationView.setupWithNavController(navController)

    }

    override fun onSupportNavigateUp(): Boolean {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation
        return navController.navigateUp(drawerLayout)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawerLayout;
    private NavController navController;
    private NavigationView navigationView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = findViewById(R.id.drawer_layout);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        navController = navHostFragment.getNavController();
        navigationView = findViewById(R.id.nav_view);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Show and Manage the Drawer and Back Icon
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        NavigationUI.setupWithNavController(navigationView, navController);

    }

    @Override
    public boolean onSupportNavigateUp() {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation.
        return NavigationUI.navigateUp(navController, drawerLayout);

    }
}

يمكنك استخدام هذه الطريقة نفسها مع كلٍّ من التنقل القائم على Bottom NavigationView والتنقل القائم على القائمة. راجع تحديث مكونات واجهة المستخدم باستخدام NavigationUI للحصول على مزيد من الأمثلة.

إضافة وجهات النشاط

بعد توصيل كل شاشة في تطبيقك بالشبكة لاستخدام مكوِّن التنقل وعدم استخدام FragmentTransactions للانتقال بين الوجهات المستندة إلى الأجزاء، تتمثل الخطوة التالية في استبعاد طلبات startActivity.

أولاً، حدِّد الأماكن التي يتوفّر فيها رسمان بيانيان منفصلان للتنقّل في تطبيقك وتستخدم startActivity للانتقال بينهما.

يحتوي هذا المثال على رسمين بيانيين (A وB) واستدعاء startActivity() للانتقال من A إلى B.

Kotlin

fun navigateToProductDetails(productId: String) {
    val intent = Intent(this, ProductDetailsActivity::class.java)
    intent.putExtra(KEY_PRODUCT_ID, productId)
    startActivity(intent)
}

Java

private void navigateToProductDetails(String productId) {
    Intent intent = new Intent(this, ProductDetailsActivity.class);
    intent.putExtra(KEY_PRODUCT_ID, productId);
    startActivity(intent);

بعد ذلك، استبدِلها بوجهة نشاط في الرسم البياني أ التي تمثل الانتقال إلى نشاط المضيف للرسم البياني ب. إذا كان لديك وسيطات تمريرها إلى وجهة بداية الرسم البياني ب، يمكنك تعيينها في تعريف وجهة النشاط.

في المثال التالي، يحدّد الرسم البياني "أ" وجهة نشاط تأخذ وسيطة product_id مع إجراء. لا يحتوي الرسم البياني ب على أي تغييرات.

قد يبدو تمثيل XML للرسمين البيانيين A وB على النحو التالي:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list_fragment">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>

    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details"
        tools:layout="@layout/product_details_host">

        <argument
            android:name="product_id"
            app:argType="integer" />

    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details_fragment">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

يمكنك الانتقال إلى نشاط المضيف للرسم البياني ب باستخدام الآليات نفسها التي تستخدمها للانتقال إلى وجهات التقسيم:

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

تمرير وسيطات وجهة النشاط إلى جزء وجهة البداية

إذا تلقّى نشاط الوجهة إضافات، كما في المثال السابق، يمكنك تمريرها إلى وجهة البداية مباشرةً كوسيطات، ولكن عليك ضبط الرسم البياني للتنقل الخاص بمضيفك يدويًا داخل طريقة onCreate() الخاصة بنشاط المضيف كي تتمكّن من تمرير المكوّنات الإضافية كوسيطات إلى الجزء، كما هو موضّح أدناه:

Kotlin

class ProductDetailsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_details_host)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment
        val navController = navHostFramgent.navController
        navController
                .setGraph(R.navigation.product_detail_graph, intent.extras)
    }

}

Java

public class ProductDetailsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.product_details_host);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        NavController navController = navHostFragment.getNavController();
        navController
                .setGraph(R.navigation.product_detail_graph, getIntent().getExtras());
    }

}

يمكن سحب البيانات من وسيطات التجزئة Bundle باستخدام فئة الوسيطات التي تم إنشاؤها، كما هو موضّح في المثال التالي:

Kotlin

class ProductDetailsFragment : Fragment() {

    val args by navArgs<ProductDetailsArgs>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val productId = args.productId
        ...
    }
    ...

Java

public class ProductDetailsFragment extends Fragment {

    ProductDetailsArgs args;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        args = ProductDetailsArgs.fromBundle(requireArguments());
    }

    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
       int productId = args.getProductId();
       ...
    }
    ...

دمج الأنشطة

يمكنك الجمع بين الرسوم البيانية للتنقّل في الحالات التي تتشارك فيها أنشطة متعدّدة التنسيق نفسه، مثل عنصر FrameLayout بسيط يحتوي على جزء واحد. في معظم هذه الحالات، يمكنك فقط دمج جميع العناصر من كل رسم بياني للتنقّل وتعديل أي عناصر وجهة نشاط إلى وجهات للأجزاء.

يجمع المثال التالي بين الرسمين البيانيين A وB من القسم السابق:

قبل الدمج:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>
    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details Host"
        tools:layout="@layout/product_details_host">
        <argument android:name="product_id"
            app:argType="integer" />
    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_detail_graph"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

بعد الدمج:

<!-- Combined Graph A and B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details" />
    </fragment>

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

إنّ الإبقاء على أسماء الإجراءات كما هي أثناء الدمج قد يجعل هذه العملية سلسة، وبدون الحاجة إلى إجراء تغييرات على الرموز البرمجية الحالية. على سبيل المثال، تظل navigateToProductDetail كما هي هنا. الفرق الوحيد هو أنّ هذا الإجراء يمثّل الآن التنقّل إلى وجهة جزء ضمن علامة NavHost نفسها بدلاً من وجهة نشاط معيّنة:

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

مراجع إضافية

لمزيد من المعلومات المتعلقة بالتنقل، راجع الموضوعات التالية: