تعامل برنامه‌ریزی با مؤلفه Navigation

مؤلفه 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 شما اجازه می دهد تا دکمه Back را فشار دهد. همچنین می‌توانید با افزودن app:defaultNavHost="true" این رفتار را در NavHost XML خود پیاده‌سازی کنید. اگر رفتار دکمه بازگشت سفارشی را پیاده‌سازی می‌کنید و نمی‌خواهید NavHost شما دکمه Back را رهگیری کند، می‌توانید 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 2.3 و بالاتر، NavBackStackEntry به SavedStateHandle دسترسی می دهد. SavedStateHandle یک نقشه کلید-مقدار است که می تواند برای ذخیره و بازیابی داده ها استفاده شود. این مقادیر از طریق مرگ فرآیند، از جمله تغییرات پیکربندی، باقی می مانند و از طریق همان شی در دسترس باقی می مانند. با استفاده از SavedStateHandle داده شده، می توانید به داده ها بین مقصد دسترسی داشته باشید و آن را ارسال کنید. این به ویژه به عنوان مکانیزمی برای بازگرداندن اطلاعات از یک مقصد پس از بیرون آمدن از پشته مفید است.

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

کاتلین

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

در Destination B، باید نتیجه را در SavedStateHandle مقصد A با استفاده از getPreviousBackStackEntry() API 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)
            }
        }
    });
}

پشته Navigation back یک 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 2.2.0 یا نسخه قبلی استفاده می کنید، باید کارخانه خود را برای استفاده از حالت ذخیره شده با 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 Overview مراجعه کنید.

اصلاح نمودارهای ناوبری متورم

شما می توانید یک نمودار ناوبری متورم را به صورت پویا در زمان اجرا تغییر دهید.

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