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

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

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

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

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

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

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

מידע נוסף

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

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