ביצוע אינטראקציה פרוגרמטית עם רכיב הניווט

רכיב הניווט מספק דרכים פרוגרמטיות ליצירה ולאינטראקציה עם רכיבי ניווט מסוימים.

יצירת 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 של המקטע או של הפעילות. In addition, 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 באמצעות API של 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 יעד א' באמצעות ה-API של 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 או גרסה קודמת, עליך לספק מזהה משלך לשימוש היצרן Saved State with 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 זמין במאמר סקירה כללית של המודל.

שינוי תרשימי ניווט מונפחים

אפשר לשנות תרשים ניווט מורחב באופן דינמי בזמן הריצה.

לדוגמה, אם יש לך 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() כדי לוודא שהמבנה הנכון משמש בטיפול בקישורי עומק, בשחזור מצב ובמעבר להתחלה היעד של התרשים.