تعامل برنامه‌نویسی با مؤلفه «ناوبری»

مؤلفه Navigation روش‌هایی برای ایجاد و تعامل برنامه‌نویسی‌شده با برخی عناصر پیمایش ارائه می‌دهد.

ایجاد NavHostFragment

می‌توانید از NavHostFragment.create() برای ایجاد برنامه‌نویسی‌شده NavHostFragment با منبع نمودار خاصی استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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() ارسال کنید.

با شروع Navigation 2.2.0، می‌توانید با فراخوانی NavController.getBackStackEntry() و ارسال شناسه مقصد به آن، مرجعی به NavBackStackEntry برای هر مقصدی در پشته ناوبری دریافت کنید. اگر پشته قبلی‌ها حاوی بیش‌از یک نمونه از مقصد مشخص‌شده باشد، getBackStackEntry() بالاترین نمونه را از پشته برمی‌گرداند.

NavBackStackEntry برگردانده‌شده Lifecycle، ViewModelStore، و SavedStateRegistry در سطح مقصد ارائه می‌دهد. این اشیاء برای طول عمر مقصد در پشته قبلی‌ها معتبر هستند. وقتی مقصد مرتبط از پشته قبلی‌ها خارج می‌شود، Lifecycle ازبین می‌رود، وضعیت دیگر ذخیره نمی‌شود، و همه اشیای ViewModel پاک می‌شوند.

این ویژگی‌ها به شما یک Lifecycle و یک فروشگاه برای اشیاء و کلاس‌های ViewModel می‌دهند که با وضعیت ذخیره‌شده کار می‌کنند، صرف‌نظر از اینکه از چه نوع مقصدی استفاده می‌کنید. این ویژگی به‌ویژه هنگام کار با انواع مقصدهایی که به‌طور خودکار Lifecycle مرتبط ندارند، مانند مقصدهای سفارشی، مفید است.

برای مثال، می‌توانید Lifecycle یک NavBackStackEntry را همان‌طور که Lifecycle یک قطعه یا فعالیت را مشاهده می‌کنید، مشاهده کنید. علاوه‌براین، NavBackStackEntry یک LifecycleOwner است، یعنی می‌توانید هنگام مشاهده LiveData یا با دیگر مؤلفه‌های آگاه از چرخه حیات از آن استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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

هرزمان navigate() را فراخوانی کنید، وضعیت چرخه حیات به‌طور خودکار به‌روز می‌شود. وضعیت‌های چرخه حیات برای مقصدهایی که در بالای پشته قبلی‌ها نیستند از RESUMED به STARTED منتقل می‌شوند اگر مقصدها هنوز در زیر یک مقصد FloatingWindow، مانند مقصد گفتگو، قابل‌مشاهده باشند، یا درغیراین‌صورت به STOPPED منتقل می‌شوند.

درحال برگرداندن نتیجه به «مقصد» قبلی

در Navigation نسخه ۲.۳ و بالاتر، NavBackStackEntry دسترسی به SavedStateHandle را فراهم می‌کند. SavedStateHandle یک نگاشت مقدار کلیدی است که می‌توان از آن برای ذخیره و بازیابی داده‌ها استفاده کرد. این مقادیر درطول مرگ پردازش، ازجمله تغییرات پیکربندی، باقی می‌مانند و ازطریق همان شیء دردسترس می‌مانند. بااستفاده از SavedStateHandle ارائه‌شده، می‌توانید به داده‌ها دسترسی داشته باشید و آن‌ها را بین مقصدها منتقل کنید. این به‌ویژه به‌عنوان مکانیزمی برای بازگرداندن داده‌ها از مقصد پس‌از خارج شدن از پشته مفید است.

برای برگرداندن داده‌ها از «مقصد ب» به «مقصد الف»، ابتدا «مقصد الف» را طوری تنظیم کنید که منتظر نتیجه در SavedStateHandle خود باشد. برای انجام این کار، NavBackStackEntry را بااستفاده از getCurrentBackStackEntry() API بازیابی کنید و سپس observe LiveData ارائه‌شده توسط SavedStateHandle.

کاتلین

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.
    }
}

جاوا

@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.
        }
    });
}

در «مقصد ب»، باید نتیجه را در SavedStateHandle «مقصد الف» بااستفاده از میانای برنامه‌سازی کاربردی getPreviousBackStackEntry() set کنید.

کاتلین

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

جاوا

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 تبدیل می‌شود.

کاتلین

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)
        }
    })
}

جاوا

@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 را که در محدوده نمودار پیمایش است نشان می‌دهد:

کاتلین

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

جاوا

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

اگر از Navigation نسخه ۲.۲.۰ یا قدیمی‌تر استفاده می‌کنید، باید کارخانه خودتان را برای استفاده از Saved State با ViewModels ارائه دهید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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 پیوست کنید.

کاتلین

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)

جاوا

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() انجام شود تا اطمینان حاصل شود که ساختار صحیح هنگام مدیریت پیوندهای عمیق، بازیابی وضعیت، و رفتن به مقصد شروع نمودار شما استفاده می‌شود.