يقدّم مكوّن التنقّل دعمًا لتطبيقات Jetpack Compose. يمكنك التنقّل بين العناصر القابلة للتجميع مع الاستفادة من البنية الأساسية والميزات في عنصر التنقّل.
ضبط إعدادات الجهاز
لتفعيل Compose، استخدِم الملحق التالي في ملف
build.gradle
الخاص بوحدة تطبيقك:
Groovy
dependencies { def nav_version = "2.8.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
البدء
عند تنفيذ ميزة التنقّل في تطبيق، عليك تنفيذ مضيف تنقّل ورسم بياني و عنصر تحكّم. لمزيد من المعلومات، يُرجى الاطّلاع على النظرة العامة حول التنقّل.
إنشاء NavController
للحصول على معلومات عن كيفية إنشاء NavController
في Compose، اطّلِع على القسم Compose
ضمن إنشاء وحدة تحكّم في التنقّل.
إنشاء NavHost
للحصول على معلومات عن كيفية إنشاء NavHost
في ميزة "الإنشاء"، يُرجى الاطّلاع على قسم "الإنشاء"
في مقالة تصميم الرسم البياني للتنقّل.
الانتقال إلى عنصر قابل للتجميع
للحصول على معلومات عن الانتقال إلى عنصر 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)
// …
}
يساعد هذا الأسلوب في منع فقدان البيانات أثناء تغييرات الإعدادات وأي اختلافات عند تعديل العنصر المعني أو تغييره.
للحصول على شرح أكثر تفصيلاً حول سبب تجنُّب تمرير البيانات المعقدة كصعوبات، بالإضافة إلى قائمة بأنواع الصعوبات المتوافقة، اطّلِع على مقالة تمرير البيانات بين الوجهات.
روابط لصفحات معيّنة
تتيح ميزة "إنشاء مسار تنقّل" استخدام الروابط لصفحات في التطبيق التي يمكن تحديدها كجزء من دالّة
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 مع ملف
قابل للتركيب. ولا تكون هذه الروابط لصفحات في التطبيق متاحة للتطبيقات الخارجية تلقائيًا. لجعل هذه الروابط لصفحات في التطبيق متاحة خارجيًا، يجب إضافة عناصر
<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
آخر ل
فتح تطبيقك في وجهة الرابط لصفحة في التطبيق.
التنقّل المتداخل
للحصول على معلومات عن كيفية إنشاء الرسوم البيانية المدمجة للتنقّل، اطّلِع على الرسوم البيانية المدمجة.
الدمج مع شريط التنقّل السفلي
من خلال تحديد NavController
على مستوى أعلى في التسلسل الهرمي القابل للتجميع،
يمكنك ربط Navigation بمكونات أخرى، مثل مكونات التنقل في أسفل الشاشة. يتيح لك ذلك التنقّل من خلال النقر على الرموز في
الشريط السفلي.
لاستخدام مكوّنات BottomNavigation
وBottomNavigationItem
،
أضِف التبعية androidx.compose.material
إلى تطبيق Android.
Groovy
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
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
، احصل على 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
تتضمن تلقائيًا أحدث حالة.
إمكانية التشغيل التفاعلي
إذا أردت استخدام مكوّن التنقّل مع ميزة "الإنشاء"، لديك خياران:
- حدِّد رسمًا بيانيًا للتنقّل باستخدام مكوّن التنقّل للمقاطع.
- حدِّد رسمًا بيانيًا للتنقّل باستخدام
NavHost
في Compose باستخدام Compose destinations. لا يمكن إجراء ذلك إلا إذا كانت جميع الشاشات في مسار التنقّل الرسومي قابلة للتجميع.
لذلك، ننصحك باستخدام المكوّن التنقّل المستنِد إلى المقتطفات في التطبيقات المختلطة التي تتضمّن ميزتَي "إنشاء" و"عرض". ستحتوي الأجزاء بعد ذلك على شاشات مستندة إلى "العرض" وشاشات "الإنشاء" والشاشات التي تستخدم كلّ من "العرض" و"الإنشاء". بعد أن تصبح محتويات كل قطعة في أداة الإنشاء، تكون الخطوة التالية هي ربط كل هذه الشاشات معًا باستخدام أداة التنقّل في الإنشاء وإزالة كل القطع.
التنقّل من ميزة "الإنشاء" باستخدام ميزة "التنقّل" للأجزاء
لتغيير الوجهات داخل رمز الإنشاء، عليك عرض الأحداث التي يمكن تمريرها إلى أيّ عنصر قابل للتركيب في التسلسل الهرمي وتشغيله:
@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
إلى أسفل التسلسل الهرمي لميزة "الإنشاء".
ومع ذلك، فإنّ عرض الدوال البسيطة يمكن إعادة استخدامها واختبارها بشكلٍ أكبر.
الاختبار
عليك فصل رمز التنقّل عن الوجهات القابلة للتجميع لتتمكّن من اختبار
كل عنصر قابل للتجميع بشكل منفصل، بمعزل عن العنصر القابل للتجميع NavHost
.
وهذا يعني أنّه يجب عدم تمرير navController
مباشرةً إلى أي عنصر
قابل للتجميع، بل يجب تمرير طلبات استدعاء التنقّل كمَعلمات بدلاً من ذلك. يتيح ذلك اختبار
جميع العناصر القابلة للتجميع بشكلٍ فردي، لأنّها لا تتطلّب
مثيلًا من navController
في الاختبارات.
إنّ مستوى التوجيه غير المباشر الذي يوفّره composable
lambda هو ما يتيح لك
فصل رمز التنقّل عن العنصر القابل للتجميع نفسه. ويمكن إجراء ذلك بطريقتين:
- لا تُمرِّر سوى الوسائط التي تم تحليلها إلى العنصر القابل للتجميع.
- نقْل الدوالّ التي يجب أن يبدأها العنصر القابل للتجميع للتنقّل، بدلاً من
NavController
نفسه
على سبيل المثال، قد يتضمّن العنصر القابل للتجميع ProfileScreen
الذي يأخذ userId
كمدخل ويسمح للمستخدمين بالانتقال إلى صفحة الملف الشخصي لصديق التوقيع التالي:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
بهذه الطريقة، تعمل العناصر القابلة للتجميع في ProfileScreen
بشكل مستقل عن التنقّل،
ما يتيح اختبارها بشكل مستقل. ستُغلِّف دالة composable
lambda
الحد الأدنى من المنطق المطلوب لسد الفجوة بين واجهات برمجة التطبيقات
للتنقّل والعنصر القابل للتجميع:
@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
وإجراءات التنقّل التي يتم تمريرها إلى العناصر القابلة للتجميع، بالإضافة إلى
العناصر القابلة للتجميع الفردية للشاشة.
اختبار 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()
}
}
اختبار إجراءات التنقّل
يمكنك اختبار تنفيذ التنقّل بطرق متعدّدة، من خلال تنفيذ نقرات على عناصر واجهة المستخدم، ثم التحقّق من الوجهة المعروضة أو مقارنة المسار المتوقّع بالمسار الحالي.
بما أنّك تريد اختبار تنفيذ تطبيقك المحدّد، يُفضَّل استخدام النقرات على واجهة المستخدم. للتعرّف على كيفية اختبار ذلك إلى جانب الدوالّ القابلة للتجميع الفردية بشكل منفصل، احرص على الاطّلاع على الاختبار في 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 codelab. للاطّلاع على مزيد من المعلومات عن الاختبار المتقدّم لرمز التنقّل، يُرجى الانتقال إلى دليل اختبار التنقّل.
مزيد من المعلومات
للاطّلاع على مزيد من المعلومات عن Jetpack Navigation، يمكنك الاطّلاع على مقالة البدء في استخدام ملف تعريف الارتباط أو المشاركة في الدورة التدريبية حول رمز Jetpack Compose Navigation.
للتعرّف على كيفية تصميم تنقّل تطبيقك ليتلاءم مع أحجام الشاشة واتجاهاتها وأشكالها المختلفة، اطّلِع على مقالة التنقّل في واجهات المستخدم السريعة الاستجابة.
للتعرّف على طريقة أكثر تقدمًا لتنفيذ تنقّل Compose في تطبيق مُقسَّم إلى وحدات، بما في ذلك مفاهيم مثل الرسوم البيانية المتداخلة ودمج شريط التنقّل في أسفل الشاشة، يمكنك الاطّلاع على تطبيق Now in Android على GitHub.
نماذج
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تصميم Material Design 2 في ميزة "الإنشاء"
- نقل بيانات واجهة التنقّل في Jetpack إلى واجهة التنقّل المطوَّرة باستخدام Compose
- مكان رفع state