העברת הניווט ב-Jetpack לכתיבה קולית

Navigation Compose API מאפשר לכם לנווט בין פונקציות Composable באפליקציית Compose, תוך ניצול היתרונות של הרכיב, התשתית והתכונות של Jetpack Navigation.

בדף הזה מוסבר איך להעביר נתונים מ-Jetpack Navigation שמבוסס על Fragment ל-Navigation Compose, כחלק מההעברה הגדולה יותר של ממשק משתמש שמבוסס על View ל-Jetpack פיתוח נייטיב.

דרישות מוקדמות להעברה

אפשר לעבור אל Navigation Compose אחרי שמסירים את כל המקטעים (fragments) ומחליפים אותם ברכיבי Composable מקבילים ברמת המסך. רכיבי Composable ברמת המסך יכולים להכיל שילוב של תוכן פיתוח נייטיב ותוכן View, אבל כל יעדי הניווט חייבים להיות רכיבי Composable כדי לאפשר העברה של Navigation Compose. עד אז, כדאי להמשיך להשתמש ברכיב הניווט מבוסס-מקטע (fragment) בבסיס הקוד של View ופיתוח נייטיב. מידע נוסף זמין במסמכי העזרה בנושא יכולת פעולה הדדית של מערכות ניווט.

שלבים בהעברה

בין אם אתם פועלים לפי אסטרטגיית המיגרציה המומלצת שלנו או בוחרים בגישה אחרת, תגיעו למצב שבו כל יעדי הניווט הם רכיבי Composable של מסכים, ורכיבי Fragment משמשים רק כמאגרי Composable. בשלב הזה, אפשר לבצע מיגרציה ל-Navigation Compose.

אם האפליקציה כבר פועלת לפי דפוס עיצוב של UDF והמדריך שלנו לארכיטקטורה, המעבר ל-Jetpack פיתוח נייטיב ול-Navigation Compose לא אמור לדרוש שינויים משמעותיים בשכבות אחרות של האפליקציה, מלבד שכבת ממשק המשתמש.

כדי להעביר את האפליקציה ל-Navigation Compose, פועלים לפי השלבים הבאים:

  1. מוסיפים את התלות ב-Navigation Compose לאפליקציה.
  2. יוצרים רכיב App-level שאפשר להרכיב ומוסיפים אותו ל-Activity כנקודת הכניסה של Compose, במקום ההגדרה של פריסת View:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. יוצרים סוגים לכל יעד ניווט. משתמשים ב-data object ליעדים שלא נדרשים בהם נתונים, וב-data class או ב-class ליעדים שנדרשים בהם נתונים.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. מגדירים את NavController במקום שבו לכל הקומפוזיציות שצריכות להפנות אליו יש גישה אליו (בדרך כלל בתוך הקומפוזיציה App). הגישה הזו מבוססת על עקרונות של העלאת הרמה של מצב (state hoisting), ומאפשרת להשתמש ב-NavController כמקור אמין לניווט בין מסכים קומפוזביליים ולשמירה על מקבץ פעילויות קודמות (back stack):

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. יוצרים את NavHost של האפליקציה בתוך ה-composable‏ App ומעבירים את navController:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. מוסיפים את יעדי composable כדי ליצור את גרף הניווט. אם כל מסך הועבר בעבר ל-Compose, השלב הזה כולל רק חילוץ של רכיבי המסך האלה מ-Fragments אל composable היעדים:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. אם פעלתם לפי ההנחיות בנושא ארכיטקטורה של ממשק משתמש ב-Compose, ובמיוחד לפי ההנחיות לגבי האופן שבו צריך להעביר את ViewModel ואת אירועי הניווט לרכיבי Composable, השלב הבא הוא לשנות את האופן שבו אתם מספקים את ViewModel לכל רכיב Composable של מסך. לרוב אפשר להשתמש בהחדרה של Hilt ובנקודת האינטגרציה שלה עם Compose ו-Navigation באמצעות hiltViewModel:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. מחליפים את כל הקריאות לניווט findNavController() בקריאות navController, ומעבירים אותן כאירועי ניווט לכל מסך קומפוזבילי, במקום להעביר את כל navController. הגישה הזו תואמת לשיטות המומלצות לחשיפת אירועים מפונקציות שאפשר להרכיב למתקשרים, ושומרת על navController כמקור המהימן היחיד.

    אפשר להעביר נתונים ליעד על ידי יצירת מופע של מחלקת המסלול שהוגדרה ליעד הזה. אפשר לקבל אותו ישירות מהערך של מקבץ פעילויות קודמות (back stack) ביעד או מ-ViewModel באמצעות SavedStateHandle.toRoute().

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. מסירים את כל המקטעים (Fragments), פריסות ה-XML הרלוונטיות, רכיבי הניווט המיותרים ומשאבים אחרים, וכן רכיבים תלויים ישנים של מקטעים ושל Jetpack Navigation.

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

תרחישים נפוצים לדוגמה

לא משנה באיזה רכיב Navigation אתם משתמשים, אותם עקרונות של ניווט חלים.

תרחישים נפוצים להעברה:

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

אחזור נתונים מורכבים במהלך הניווט

מומלץ מאוד לא להעביר אובייקטים מורכבים של נתונים במהלך הניווט. במקום זאת, מעבירים את המידע המינימלי הנדרש, כמו מזהה ייחודי או סוג אחר של מזהה, כארגומנטים כשמבצעים פעולות ניווט. מומלץ לאחסן אובייקטים מורכבים כנתונים במקור אחד לנתוני האמת, כמו שכבת הנתונים. מידע נוסף זמין במאמר אחזור נתונים מורכבים במהלך הניווט.

אם רכיבי ה-Fragment מעבירים אובייקטים מורכבים כארגומנטים, כדאי לשנות את מבנה הקוד כך שיהיה אפשר לאחסן את האובייקטים האלה בשכבת הנתונים ולאחזר אותם ממנה. דוגמאות לכך אפשר לראות במאגר Now in Android.

מגבלות

בקטע הזה מתוארות המגבלות הנוכחיות של Navigation Compose.

העברה מצטברת ל-Navigation Compose

בשלב הזה, אי אפשר להשתמש ב-Navigation Compose כשעדיין משתמשים ב-Fragments כיעדים בקוד. כדי להתחיל להשתמש ב-Navigation Compose, כל היעדים צריכים להיות פונקציות שאפשר להוסיף לקומפוזיציה. אפשר לעקוב אחרי הגשת בקשה להוספת תכונה ב-Issue Tracker.

אנימציות של מעברים

החל מגרסה Navigation 2.7.0-alpha01, התמיכה בהגדרת מעברים מותאמים אישית, שבעבר הייתה זמינה מ-AnimatedNavHost, זמינה עכשיו ישירות ב-NavHost. מידע נוסף זמין בהערות המוצר.

מידע נוסף

למידע נוסף על מעבר ל-Navigation Compose, אפשר לעיין במקורות המידע הבאים:

  • Navigation Compose codelab: ב-codelab הזה מוסבר איך להשתמש ב-Navigation Compose.
  • עכשיו במאגר Now in Android: אפליקציית Android פונקציונלית לחלוטין שנבנתה לגמרי באמצעות Kotlin ו-Jetpack פיתוח נייטיב, בהתאם לשיטות המומלצות לעיצוב ולפיתוח של Android, וכוללת Navigation Compose.
  • העברת Sunflower ל-Jetpack פיתוח נייטיב: פוסט בבלוג שמתעד את תהליך ההעברה של אפליקציית הדוגמה Sunflower מ-Views ל-Compose, כולל העברה ל-Navigation Compose.
  • Jetnews לכל מסך: פוסט בבלוג שמתעד את ארגון הקוד מחדש (Refactoring) וההעברה של דוגמת Jetnews כדי לתמוך בכל המסכים באמצעות Jetpack פיתוח נייטיב ו-Navigation Compose.