التفاعل آليًا مع مكوّن التنقل

يوفر مكون التنقل طرقًا لإنشاء عناصر تنقل معينة والتفاعل معها آليًا.

إنشاء NavHostFragment

يمكنك استخدام NavHostFragment.create() لإنشاء NavHostFragment آليًا باستخدام مورد رسم بياني محدّد، كما هو موضّح في المثال أدناه:

Kotlin

val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit()

Java

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit();

يُرجى العِلم أنّ سياسة setPrimaryNavigationFragment(finalHost) تتيح لنظام NavHost اعتراض الضغط على زر الرجوع في النظام. يمكنك أيضًا تنفيذ هذا الإجراء في ملف XML الخاص بـ NavHost من خلال إضافة السمة app:defaultNavHost="true". إذا كنت تنفّذ سلوكًا مخصّصًا لزر الرجوع ولا تريد أن يضغط NavHost على زر الرجوع، يمكنك تمرير null إلى setPrimaryNavigationFragment().

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

تعرض سمة NavBackStackEntry المعروضة Lifecycle و ViewModelStore و SavedStateRegistry على مستوى الوجهة. هذه العناصر صالحة طوال عمر الوجهة في الحزمة الخلفية. عندما تظهر الوجهة المرتبطة من الحزمة الخلفية، يتم محو Lifecycle ولا يتم حفظ الحالة، ويتم محو أي عناصر ViewModel.

توفّر لك هذه السمات Lifecycle ومتجرًا لعناصر ViewModel وفئات متوافقة مع الحالة المحفوظة بغض النظر عن نوع الوجهة التي تستخدمها. ويُعدّ ذلك مفيدًا بشكلٍ خاص عند العمل مع أنواع الوجهات التي لا ترتبط تلقائيًا بسمة Lifecycle، مثل الوجهات المخصّصة.

على سبيل المثال، يمكنك الاطّلاع على Lifecycle لـ NavBackStackEntry تمامًا كما ستلاحظ Lifecycle لجزء أو نشاط. بالإضافة إلى ذلك، NavBackStackEntry هو LifecycleOwner، ما يعني أنّه يمكنك استخدامه عند ملاحظة LiveData أو مع مكوِّنات أخرى حسب مراحل نشاط الحياة، كما هو موضّح في المثال التالي:

Kotlin

myViewModel.liveData.observe(backStackEntry, Observer { myData ->
    // react to live data update
})

Java

myViewModel.getLiveData().observe(backStackEntry, myData -> {
    // react to live data update
});

يتم تعديل حالة مراحل النشاط تلقائيًا عند الاتصال بالرقم navigate(). يتم نقل حالات مراحل النشاط للوجهات التي لا تقع في أعلى الحزمة الخلفية من RESUMED إلى STARTED إذا كانت الوجهات لا تزال مرئية ضمن وجهة FloatingWindow، مثل وجهة مربّع حوار، أو إلى STOPPED بطريقة أخرى.

عرض نتيجة إلى الوجهة السابقة

في شريط التنقل 2.3 والإصدارات الأحدث، يتيح NavBackStackEntry الوصول إلى SavedStateHandle. SavedStateHandle هي خريطة مفتاح/قيمة يمكن استخدامها لتخزين البيانات واستردادها. وتستمر هذه القيم حتى انتهاء العملية، بما في ذلك تغييرات التكوين، وتظل متاحة من خلال نفس الكائن. باستخدام السمة SavedStateHandle المحددة، يمكنك الوصول إلى البيانات وتمريرها بين الوجهات. هذه الطريقة مفيدة بشكل خاص كآلية لاسترجاع البيانات من وجهة بعد خروجها من المكدس.

لتمرير البيانات مرة أخرى إلى الوجهة "أ" من الوجهة "ب"، يجب أولاً إعداد الوجهة "أ" للاستماع إلى نتيجة في SavedStateHandle. لإجراء ذلك، استرِد NavBackStackEntry باستخدام واجهة برمجة تطبيقات getCurrentBackStackEntry()، ثم observe LiveData المقدَّمة من SavedStateHandle.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
            .getSavedStateHandle()
            .getLiveData("key");
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            // Do something with the result.
        }
    });
}

في الوجهة "ب"، يجب set النتيجة في SavedStateHandle للوجهة أ باستخدام getPreviousBackStackEntry() واجهة برمجة التطبيقات.

Kotlin

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

Java

navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);

إذا أردت معالجة نتيجة واحدة فقط، يجب طلب الرمز remove() على SavedStateHandle لمحو النتيجة. إذا لم تتم إزالة النتيجة، ستواصل LiveData عرض النتيجة الأخيرة إلى أي حالات Observer جديدة.

الاعتبارات الواجب مراعاتها عند استخدام وجهات مربّعات الحوار

عند navigate إلى وجهة توفّر العرض الكامل لـ NavHost (مثل وجهة <fragment>)، يتم إيقاف مراحل نشاط الوجهة السابقة، ما يمنع أي معاودة اتصال إلى LiveData المقدَّمة من خلال SavedStateHandle.

مع ذلك، عند الانتقال إلى وجهة محادثة، تظهر الوجهة السابقة أيضًا على الشاشة وبالتالي هي STARTED على الرغم من أنها ليست الوجهة الحالية. وهذا يعني أنّ المكالمات الواردة إلى getCurrentBackStackEntry() من خلال طرق مراحل النشاط، مثل onViewCreated() ستعرض NavBackStackEntry من وجهة مربّع الحوار بعد تغيير في الإعدادات أو إيقاف العملية وإعادة الترفيه (نظرًا لإعادة عرض مربّع الحوار فوق الوجهة الأخرى). لذلك، يجب استخدام السمة getBackStackEntry() مع رقم تعريف وجهتك لضمان استخدام العلامة NavBackStackEntry الصحيحة دائمًا.

وهذا يعني أيضًا أنّه سيتم تشغيل أي Observer تضبطه في النتيجة LiveData حتى عندما لا تزال وجهات مربّع الحوار تظهر على الشاشة. إذا كنت تريد الاطّلاع فقط على النتيجة عند إغلاق وجهة مربّع الحوار وتصبح الوجهة الأساسية هي الوجهة الحالية، يمكنك ملاحظة Lifecycle المرتبطة بـ NavBackStackEntry واسترداد النتيجة فقط عندما تصبح RESUMED.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navController = findNavController();
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment)

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains("key")) {
            val result = navBackStackEntry.savedStateHandle.get<String>("key");
            // Do something with the result
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    NavController navController = NavHostFragment.findNavController(this);
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment);

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    final LifecycleEventObserver observer = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_RESUME)
                && navBackStackEntry.getSavedStateHandle().contains("key")) {
                String result = navBackStackEntry.getSavedStateHandle().get("key");
                // Do something with the result
            }
        }
    };
    navBackStackEntry.getLifecycle().addObserver(observer);

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_DESTROY)) {
                navBackStackEntry.getLifecycle().removeObserver(observer)
            }
        }
    });
}

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

يوضّح المثال التالي كيفية استرداد ViewModel تم تحديده في رسم بياني للتنقّل:

Kotlin

val viewModel: MyViewModel
        by navGraphViewModels(R.id.my_graph)

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);

إذا كنت تستخدم الإصدار 2.2.0 أو إصدارًا أقدم، عليك تحديد المصنع الخاص بك لاستخدام الحالة المحفوظة مع ViewModels، كما هو موضح في المثال التالي:

Kotlin

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) {
    SavedStateViewModelFactory(requireActivity().application, requireParentFragment())
}

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

ViewModelProvider viewModelProvider = new ViewModelProvider(
        backStackEntry.getViewModelStore(),
        new SavedStateViewModelFactory(
                requireActivity().getApplication(), requireParentFragment()));

MyViewModel myViewModel = provider.get(myViewModel.getClass());

لمزيد من المعلومات عن ViewModel، يُرجى الاطّلاع على نظرة عامة على ViewModel.

تعديل الرسوم البيانية المضخَّمة للتنقل

ويمكنك تعديل رسم بياني متضخم للتنقل بشكل ديناميكي في وقت التشغيل.

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

ضع في الاعتبار NavGraph هذا:

<?xml version="1.0" encoding="utf-8"?>
<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/nav_graph"
    app:startDestination="@id/home">
    <fragment
        android:id="@+id/home"
        android:name="com.example.android.navigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/location"
        android:name="com.example.android.navigation.LocationFragment"
        android:label="fragment_location"
        tools:layout="@layout/fragment_location" />
    <fragment
        android:id="@+id/shop"
        android:name="com.example.android.navigation.ShopFragment"
        android:label="fragment_shop"
        tools:layout="@layout/fragment_shop" />
    <fragment
        android:id="@+id/settings"
        android:name="com.example.android.navigation.SettingsFragment"
        android:label="fragment_settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

عند تحميل هذا الرسم البياني، تحدّد السمة app:startDestination أنه سيتم عرض HomeFragment. لتجاوز وجهة البداية بشكل ديناميكي، قم بما يلي:

  1. عليك أولاً تضخيم NavGraph يدويًا.
  2. إلغاء وجهة البدء.
  3. وأخيرًا، أرفق الرسم البياني يدويًا بـ NavController.

Kotlin

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

val navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph)
navGraph.startDestination = R.id.shop
navController.graph = navGraph
binding.bottomNavView.setupWithNavController(navController)

Java

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
        .findFragmentById(R.id.nav_host_fragment);

NavController navController = navHostFragment.getNavController();
NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph);
navGraph.setStartDestination(R.id.shop);
navController.setGraph(navGraph);
NavigationUI.setupWithNavController(binding.bottomNavView, navController);

الآن عند بدء تشغيل تطبيقك، يتم عرض ShopFragment بدلاً من HomeFragment.

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

يُرجى العِلم أنّ هذه التقنية تتيح أيضًا إلغاء الجوانب الأخرى للسمة NavGraph حسب الحاجة. يجب إجراء جميع التعديلات على الرسم البياني قبل استدعاء الدالة setGraph() لضمان استخدام البنية الصحيحة عند التعامل مع الروابط لمواضع معيّنة واستعادة الحالة والانتقال إلى وجهة بداية الرسم البياني.