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

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

إنشاء 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 نظام اعتراض الضغط على زر الرجوع. يمكنك أيضًا تنفيذ هذا السلوك في ملف NavHost XML عن طريق إضافة 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، يمكنك الوصول إلى البيانات وتمريرها بين الوجهات. وهذا مفيد بشكل خاص كآلية لاسترداد البيانات من الوجهة بعد خروجه من المكدس.

لتمرير البيانات مرة أخرى إلى الوجهة "أ" من الوجهة "ب"، عليك أولاً إعداد الوجهة A للاستماع إلى نتيجة على SavedStateHandle. لإجراء ذلك، يمكنك استرداد NavBackStackEntry باستخدام getCurrentBackStackEntry() API ثم 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() للتأكد من أن البنية الصحيحة يُستخدم عند التعامل مع الروابط لمواضع معيّنة واستعادة الحالة والانتقال إلى نقطة البداية وجهة الرسم البياني.