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

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

בדף הזה מוסבר איך לעבור מ-Jetpack Navigation שמבוסס על קטעים (fragments) ל-Navigation Compose, כחלק מהמעבר המקיף יותר מממשק משתמש שמבוסס על תצוגות (views) ל-Jetpack Compose.

תנאים מוקדמים להעברה

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

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

שלבי ההעברה

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

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

כדי לעבור ל'כתיבה עם ניווט':

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

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

    @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, השלב הזה כולל רק חילוץ של רכיבי ה-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 ואירועי ניווט לרכיבי Compose, השלב הבא הוא לשנות את האופן שבו אתם מספקים את ViewModel לכל רכיב Compose במסך. לעיתים קרובות אפשר להשתמש בהזרקה של 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, את הניווט הלא הכרחי ואת המשאבים האחרים, ואת יחסי התלות הלא תקינים של Fragment ו-Jetpack Navigation.

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

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

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

תרחישים נפוצים לדוגמה במהלך ההעברה:

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

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

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

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

מגבלות

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

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

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

אנימציות מעבר

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

מידע נוסף

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

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