רכיב הניווט מספק תמיכה באפליקציות Jetpack פיתוח נייטיב. אתם יכולים לנווט בין קומפוזיציות תוך ניצול התשתית והתכונות של רכיב הניווט.
לספריית הניווט העדכנית ביותר בגרסת אלפא, שנוצרה במיוחד עבור Compose, אפשר לעיין במסמכי התיעוד של Navigation 3.
הגדרה
כדי לתמוך ב-Compose, צריך להשתמש בתלות הבאה בקובץ build.gradle
של מודול האפליקציה:
Groovy
dependencies { def nav_version = "2.9.4" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.4" implementation("androidx.navigation:navigation-compose:$nav_version") }
שנתחיל?
כשמטמיעים ניווט באפליקציה, צריך להטמיע מארח ניווט, גרף ובקר. מידע נוסף זמין במאמר בנושא ניווט.
יצירת NavController
בקטע Compose במאמר יצירת בקר ניווט מוסבר איך ליצור NavController
ב-Compose.
יצירת NavHost
מידע על יצירת NavHost
ב-Compose זמין בקטע בנושא Compose במאמר תכנון גרף הניווט.
ניווט אל קומפוזבל
מידע על ניווט אל Composable מופיע במאמר ניווט אל יעד במסמכי הארכיטקטורה.
ניווט באמצעות ארגומנטים
מידע על העברת ארגומנטים בין יעדים שניתנים להרכבה מופיע בקטע Compose במאמר תכנון תרשים הניווט.
אחזור נתונים מורכבים במהלך הניווט
מומלץ מאוד לא להעביר אובייקטים מורכבים של נתונים כשמנווטים, אלא להעביר את המידע המינימלי הנדרש, כמו מזהה ייחודי או סוג אחר של מזהה, כארגומנטים כשמבצעים פעולות ניווט:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
אובייקטים מורכבים צריכים להיות מאוחסנים כנתונים במקור יחיד מהימן, כמו שכבת הנתונים. אחרי שמגיעים ליעד אחרי הניווט, אפשר לטעון את המידע הנדרש ממקור האמת היחיד באמצעות המזהה שהועבר. כדי לאחזר את הארגומנטים ב-ViewModel
שאחראי לגישה לשכבת הנתונים, משתמשים ב-SavedStateHandle
של ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
הגישה הזו עוזרת למנוע אובדן נתונים במהלך שינויים בהגדרות, וגם חוסר עקביות כשמעדכנים או משנים את האובייקט הרלוונטי.
הסבר מפורט יותר על הסיבות לכך שכדאי להימנע מהעברת נתונים מורכבים כארגומנטים, ורשימה של סוגי הארגומנטים הנתמכים, מופיעים במאמר העברת נתונים בין יעדים.
קישורי עומק
Navigation Compose תומך גם בקישורי עומק שאפשר להגדיר כחלק מהפונקציה composable()
. הפרמטר deepLinks
שלו מקבל רשימה של אובייקטים מסוג NavDeepLink
שאפשר ליצור במהירות באמצעות ה-method navDeepLink()
:
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
קישורי העומק האלה מאפשרים לשייך כתובת URL, פעולה או סוג MIME ספציפיים לרכיב. כברירת מחדל, קישורי העומק האלה לא נחשפים לאפליקציות חיצוניות. כדי שקישורי העומק האלה יהיו זמינים חיצונית, צריך להוסיף את רכיבי <intent-filter>
המתאימים לקובץ manifest.xml
של האפליקציה. כדי להפעיל את הקישור העמוק בדוגמה הקודמת, צריך להוסיף את הקוד הבא בתוך האלמנט <activity>
של המניפסט:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
הניווט יוצר אוטומטית קישור עומק לאותו רכיב שאפשר להרכיב כשקישור העומק מופעל על ידי אפליקציה אחרת.
אפשר להשתמש באותם קישורי עומק גם כדי ליצור PendingIntent
עם קישור העומק המתאים מתוך רכיב שאפשר להרכיב:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
אחר כך תוכלו להשתמש ב-deepLinkPendingIntent
כמו בכל PendingIntent
אחר כדי לפתוח את האפליקציה ביעד של קישור העומק.
ניווט ברמות שונות
מידע על יצירת תרשימי ניווט בתצוגת עץ זמין במאמר בנושא תרשימים בתצוגת עץ.
איך יוצרים סרגל ניווט תחתון וסרגל ניווט דינמיים
NavigationSuiteScaffold
מציג את ממשק המשתמש המתאים לניווט בהתאם לWindowSizeClass
שבו האפליקציה מוצגת. במסכים קומפקטיים, NavigationSuiteScaffold
מציג סרגל ניווט תחתון. במסך מורחב, מוצג במקום זאת סרגל ניווט.
מידע נוסף זמין במאמר יצירת ניווט מותאם.
יכולת פעולה הדדית
אם רוצים להשתמש ברכיב Navigation עם Compose, יש שתי אפשרויות:
- הגדרת תרשים ניווט באמצעות רכיב הניווט עבור פרגמנטים.
- מגדירים גרף ניווט עם
NavHost
ב-Compose באמצעות יעדי Compose. זה אפשרי רק אם כל המסכים בתרשים הניווט הם קומפוזיציות.
לכן, ההמלצה לאפליקציות עם תמהיל של Compose ו-Views היא להשתמש ברכיב Fragment-based Navigation. לאחר מכן, הפעולות יתבצעו על מסכים מבוססי-View, מסכי Compose ומסכים שמשתמשים גם ב-Views וגם ב-Compose. אחרי שמעבירים את התוכן של כל Fragment ל-Compose, השלב הבא הוא לקשר בין כל המסכים באמצעות Navigation Compose ולהסיר את כל ה-Fragments.
ניווט מ-Compose באמצעות Navigation for fragments
כדי לשנות יעדים בתוך קוד Compose, צריך לחשוף אירועים שאפשר להעביר ולהפעיל על ידי כל רכיב שאפשר להרכיב בהיררכיה:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
בקטע הקוד, יוצרים את הגשר בין Compose לבין רכיב הניווט מבוסס-קטעי הקוד על ידי מציאת NavController
וניווט ליעד:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
אפשרות אחרת היא להעביר את NavController
בהיררכיית ה-Compose.
עם זאת, חשיפה של פונקציות פשוטות מאפשרת שימוש חוזר וביצוע בדיקות הרבה יותר בקלות.
בדיקה
כדי לבדוק כל קומפוזיציה בנפרד, בלי קשר לקומפוזיציה NavHost
, צריך להפריד את קוד הניווט מהיעדים הניתנים לקומפוזיציה.
כלומר, אסור להעביר את navController
ישירות לכלרכיב שאפשר להרכיב, אלא צריך להעביר פונקציות קריאה חוזרת של ניווט כפרמטרים. כך אפשר לבדוק כל קומפוזיציה בנפרד, כי לא צריך מופע של navController
בבדיקות.
רמת ההפניה העקיפה שמספקת פונקציית ה-lambda composable
מאפשרת להפריד את קוד הניווט מהקומפוזיציה עצמה. הפעולה הזו מתבצעת בשני כיוונים:
- העברה רק של ארגומנטים מנותחים לרכיב ה-Composable
- צריך להעביר פונקציות למדא שמופעלות על ידי הפונקציה הניתנת להרכבה כדי לנווט, ולא את
NavController
עצמו.
לדוגמה, ProfileScreen
קומפוזבל שמקבל userId
כקלט ומאפשר למשתמשים לנווט לדף הפרופיל של חבר יכול להיות עם החתימה הבאה:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
כך, רכיב ה-ProfileScreen
composable פועל בנפרד מהרכיב Navigation, ואפשר לבדוק אותו בנפרד. פונקציית ה-lambda composable
תכיל את הלוגיקה המינימלית שנדרשת כדי לגשר על הפער בין ממשקי ה-API של Navigation לבין הרכיב הניתן להרכבה:
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
מומלץ לכתוב בדיקות שמכסות את דרישות הניווט באפליקציה על ידי בדיקת NavHost
, פעולות הניווט שמועברות לרכיבי ה-Composable, וגם רכיבי ה-Composable של המסכים השונים.
בדיקת NavHost
כדי להתחיל לבדוק את NavHost
, מוסיפים את יחסי התלות הבאים של בדיקת הניווט:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
עוטפים את NavHost
של האפליקציה ברכיב שאפשר להרכיב ממנו רכיבים אחרים, שמקבל NavHostController
כפרמטר.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
עכשיו אפשר לבדוק את AppNavHost
ואת כל לוגיקת הניווט שמוגדרת בתוך
NavHost
על ידי העברת מופע של ארטיפקט הבדיקה של הניווט
TestNavHostController
. בדיקת ממשק משתמש שמאמתת את יעד ההתחלה של האפליקציה שלכם, NavHost
, תיראה כך:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
בדיקת פעולות ניווט
יש כמה דרכים לבדוק את ההטמעה של הניווט. אפשר ללחוץ על רכיבי ממשק המשתמש ואז לאמת את היעד שמוצג, או להשוות בין המסלול הצפוי לבין המסלול הנוכחי.
מכיוון שאתם רוצים לבדוק את ההטמעה של האפליקציה הקונקרטית שלכם, עדיף להשתמש בקליקים בממשק המשתמש. כדי ללמוד איך לבדוק את זה לצד פונקציות הניתנות להרכבה בנפרד, כדאי לעיין ב-codelab בנושא בדיקות ב-Jetpack Compose.
אפשר גם להשתמש ב-navController
כדי לבדוק את הטענות על ידי השוואת המסלול הנוכחי למסלול הצפוי, באמצעות navController
של currentBackStackEntry
:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
לקבלת הנחיות נוספות בנושא יסודות הבדיקה ב-Compose, אפשר לעיין במאמר בדיקת פריסת Compose וב-codelab בנושא בדיקה ב-Jetpack Compose. מידע נוסף על בדיקה מתקדמת של קוד הניווט זמין במדריך בנושא בדיקת ניווט.
מידע נוסף
מידע נוסף על Jetpack Navigation זמין במאמר תחילת העבודה עם רכיב הניווט או ב-Jetpack Compose Navigation codelab.
כדי ללמוד איך לעצב את הניווט באפליקציה כך שיתאים לגדלים, לכיוונים ולגורמי צורה שונים של מסכים, אפשר לעיין במאמר ניווט בממשקי משתמש רספונסיביים.
כדי ללמוד על הטמעה מתקדמת יותר של ניווט ב-Compose באפליקציה מודולרית, כולל מושגים כמו גרפים מוטמעים ושילוב של סרגל ניווט בתחתית, אפשר לעיין באפליקציה Now in Android ב-GitHub.
טעימות
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- Material Design 2 ב-Compose
- העברה של Jetpack Navigation אל Navigation Compose
- איפה מעבירים את הסטייט