مؤلفه Navigation از برنامه های Jetpack Compose پشتیبانی می کند. میتوانید در حالی که از زیرساختها و ویژگیهای مؤلفه ناوبری بهره میبرید، بین اجزای سازنده حرکت کنید.
راه اندازی
برای پشتیبانی از Compose، از وابستگی زیر در فایل build.gradle
ماژول برنامه خود استفاده کنید:
شیار
dependencies { def nav_version = "2.8.2" implementation "androidx.navigation:navigation-compose:$nav_version" }
کاتلین
dependencies { val nav_version = "2.8.2" implementation("androidx.navigation:navigation-compose:$nav_version") }
شروع کنید
هنگام پیادهسازی ناوبری در یک برنامه، یک میزبان ناوبری، نمودار و کنترلکننده را اجرا کنید. برای اطلاعات بیشتر، به نمای کلی ناوبری مراجعه کنید.
یک NavController ایجاد کنید
برای اطلاعات در مورد نحوه ایجاد NavController
در Compose، به بخش Compose در Create a Navigation Controller مراجعه کنید.
NavHost ایجاد کنید
برای اطلاعات در مورد نحوه ایجاد NavHost
در Compose، به بخش Compose در Design your navigation graph مراجعه کنید.
به یک کامپوزیشن بروید
برای اطلاعات در مورد پیمایش به یک Composable، به پیمایش به مقصد در مستندات معماری مراجعه کنید.
با استدلال حرکت کنید
برای اطلاعات در مورد انتقال آرگومانها بین مقصدهای قابل ترکیب، به بخش نوشتن در نمودار پیمایش خود را طراحی کنید .
هنگام ناوبری داده های پیچیده را بازیابی کنید
اکیداً توصیه میشود که هنگام پیمایش، اشیاء داده پیچیده را رد نکنید، بلکه در عوض حداقل اطلاعات لازم مانند شناسه منحصربهفرد یا شکل دیگری از شناسه را به عنوان آرگومان در هنگام انجام اقدامات ناوبری ارسال کنید:
// 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
را می پذیرد که می توانند به سرعت با استفاده از متد 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 را با یک Composable مرتبط کنید. به طور پیش فرض، این پیوندهای عمیق در معرض برنامه های خارجی قرار نمی گیرند. برای در دسترس قرار دادن این پیوندهای عمیق به صورت خارجی، باید عناصر <intent-filter>
مناسب را به فایل manifest.xml
برنامه خود اضافه کنید. برای فعال کردن پیوند عمیق در مثال قبل، باید موارد زیر را در عنصر <activity>
مانیفست اضافه کنید:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
هنگامی که پیوند عمیق توسط برنامه دیگری راه اندازی می شود، پیمایش به طور خودکار به آن پیوندهای عمیق متصل می شود.
همین پیوندهای عمیق همچنین می توانند برای ساختن یک PendingIntent
با پیوند عمیق مناسب از یک composable استفاده شوند:
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
دیگری برای باز کردن برنامه خود در مقصد پیوند عمیق استفاده کنید.
ناوبری تودرتو
برای اطلاعات در مورد نحوه ایجاد نمودارهای پیمایش تو در تو، به نمودارهای تودرتو مراجعه کنید.
ادغام با نوار ناوبری پایین
با تعریف NavController
در یک سطح بالاتر در سلسله مراتب قابل ترکیب، می توانید Navigation را با اجزای دیگر مانند مؤلفه ناوبری پایین متصل کنید. انجام این کار به شما امکان می دهد با انتخاب آیکون های نوار پایین حرکت کنید.
برای استفاده از اجزای BottomNavigation
و BottomNavigationItem
، وابستگی androidx.compose.material
را به برنامه Android خود اضافه کنید.
شیار
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
کاتلین
dependencies { implementation("androidx.compose.material:material:1.7.5") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
برای پیوند دادن موارد موجود در نوار پیمایش پایین به مسیرها در نمودار ناوبری، توصیه میشود کلاسی مانند TopLevelRoute
که در اینجا مشاهده میشود، تعریف کنید که دارای یک کلاس مسیر و یک نماد باشد.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
سپس آن مسیرها را در لیستی قرار دهید که توسط BottomNavigationItem
قابل استفاده است:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
در BottomNavigation
composable خود، NavBackStackEntry
فعلی را با استفاده از تابع currentBackStackEntryAsState()
دریافت کنید. این ورودی به شما امکان دسترسی به NavDestination
فعلی را می دهد. سپس وضعیت انتخاب شده هر BottomNavigationItem
را می توان با مقایسه مسیر مورد با مسیر مقصد فعلی و مقصدهای اصلی آن برای رسیدگی به مواردی که از ناوبری تودرتو با استفاده از سلسله مراتب NavDestination
استفاده می کنید، تعیین کرد.
مسیر مورد نیز برای اتصال onClick
lambda به یک تماس برای navigate
استفاده میشود تا با ضربه زدن روی مورد به آن مورد هدایت شود. با استفاده از پرچمهای saveState
و restoreState
، حالت و پشته پشته آن آیتم به درستی ذخیره و بازیابی میشود، زیرا بین موارد پیمایش پایینی جابهجا میشوید.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
در اینجا شما از روش NavController.currentBackStackEntryAsState()
استفاده می کنید تا حالت navController
را از تابع NavHost
خارج کنید و آن را با مؤلفه BottomNavigation
به اشتراک بگذارید. این بدان معناست که BottomNavigation
به طور خودکار به روزترین حالت را دارد.
قابلیت همکاری
اگر می خواهید از مؤلفه Navigation با Compose استفاده کنید، دو گزینه دارید:
- یک نمودار ناوبری با مولفه Navigation برای قطعات تعریف کنید.
- یک نمودار ناوبری را با
NavHost
در Compose با استفاده از مقصدهای Compose تعریف کنید. این تنها در صورتی امکان پذیر است که تمام صفحه های موجود در نمودار ناوبری قابل ترکیب باشند.
بنابراین، توصیه برای برنامه های ترکیبی Compose و Views استفاده از مولفه Navigation مبتنی بر Fragment است. سپس بخشها صفحههای مبتنی بر View، صفحههای Compose و صفحههایی را که از Views و Compose استفاده میکنند، نگه میدارند. هنگامی که محتویات هر قطعه در Compose قرار گرفت، گام بعدی این است که همه آن صفحهها را با Navigation Compose به هم گره بزنید و همه قطعات را حذف کنید.
برای قطعات از Compose with Navigation پیمایش کنید
برای تغییر مقاصد در کد Compose، رویدادهایی را که میتوانند به هر composable در سلسلهمراتب منتقل شوند و توسط آنها راهاندازی شوند، نمایش میدهید:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
در قطعه خود، با یافتن NavController
و پیمایش به مقصد، پلی بین Compose و مولفه Navigation مبتنی بر قطعه ایجاد میکنید:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
از طرف دیگر، میتوانید NavController
به سلسله مراتب Compose خود منتقل کنید. با این حال، نمایش توابع ساده بسیار قابل استفاده مجدد و آزمایش پذیرتر است.
تست کردن
کد پیمایش را از مقاصد قابل ترکیب خود جدا کنید تا آزمایش هر یک از قابلیتها را به صورت مجزا، جدا از NavHost
قابل ترکیب، فعال کنید.
این به این معنی است که شما نباید navController
مستقیماً به هیچ composable منتقل کنید و در عوض تماس های ناوبری را به عنوان پارامتر ارسال کنید. این اجازه می دهد تا همه اجزای سازنده شما به صورت جداگانه قابل آزمایش باشند، زیرا در آزمایشات به نمونه ای از navController
نیاز ندارند.
سطح غیرمستقیم ارائه شده توسط لامبدای composable
، چیزی است که به شما امکان می دهد کد پیمایش خود را از خود ترکیب بندی جدا کنید. این در دو جهت کار می کند:
- فقط آرگومان های تجزیه شده را به composable خود منتقل کنید
- لامبداهایی را که باید توسط composable برای پیمایش به جای خود
NavController
فعال شوند، عبور دهید.
به عنوان مثال، یک ProfileScreen
قابل ترکیب که یک userId
به عنوان ورودی دریافت می کند و به کاربران اجازه می دهد به صفحه نمایه یک دوست حرکت کنند، ممکن است دارای امضای زیر باشد:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
به این ترتیب، Composable ProfileScreen
به طور مستقل از Navigation کار می کند و به آن اجازه می دهد مستقل آزمایش شود. لامبدای composable
حداقل منطق مورد نیاز برای پر کردن شکاف بین API های ناوبری و قابل ترکیب شما را در بر می گیرد:
@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های شما و همچنین تک تک قطعات صفحه نمایش شما ارسال میشوند، بنویسید.
تست NavHost
برای شروع آزمایش NavHost
خود، وابستگی آزمایش پیمایش زیر را اضافه کنید:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
NavHost
برنامه خود را در یک Composable بپیچید که NavHostController
را به عنوان پارامتر می پذیرد.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
اکنون می توانید AppNavHost
و تمام منطق ناوبری تعریف شده در NavHost
را با ارسال نمونه ای از مصنوع آزمایش ناوبری TestNavHostController
آزمایش کنید. یک تست UI که مقصد شروع برنامه و 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()
}
}
آزمایش اقدامات ناوبری
میتوانید پیادهسازی ناوبری خود را به روشهای مختلف، با کلیک روی عناصر رابط کاربری و سپس تأیید مقصد نمایش داده شده یا با مقایسه مسیر مورد انتظار با مسیر فعلی، آزمایش کنید.
از آنجایی که می خواهید اجرای برنامه بتن خود را آزمایش کنید، کلیک بر روی رابط کاربری ترجیح داده می شود. برای یادگیری نحوه آزمایش این مورد در کنار توابع منفرد قابل ترکیب به صورت مجزا، مطمئن شوید که Testing in Jetpack Compose Codelab را بررسی کنید.
همچنین می توانید از navController
برای بررسی ادعاهای خود با مقایسه مسیر فعلی با مسیر مورد انتظار، با استفاده از navController
's currentBackStackEntry
استفاده کنید:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
برای راهنمایی بیشتر در مورد اصول اولیه تست Compose، به تست طرحبندی Compose خود و تست در لبه کد Jetpack Compose مراجعه کنید. برای کسب اطلاعات بیشتر در مورد تست پیشرفته کد پیمایش، از راهنمای Test Navigation دیدن کنید.
بیشتر بدانید
برای کسب اطلاعات بیشتر در مورد ناوبری Jetpack، به شروع کار با مؤلفه ناوبری مراجعه کنید یا از نوار کد ناوبری Jetpack Compose استفاده کنید.
برای یادگیری نحوه طراحی ناوبری برنامه خود به گونه ای که با اندازه های مختلف صفحه نمایش، جهت گیری ها و عوامل شکل سازگار شود، به ناوبری برای رابط های کاربری پاسخگو مراجعه کنید.
برای آشنایی با پیادهسازی مسیریابی Compose پیشرفتهتر در یک برنامه مدولار شده، از جمله مفاهیمی مانند نمودارهای تودرتو و ادغام نوار پیمایش پایین، به برنامه Now in Android در GitHub نگاهی بیندازید.
نمونه ها
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- Material Design 2 در Compose
- Jetpack Navigation را به Navigation Compose منتقل کنید
- محل بالا بردن حالت