يوفّر مكوّن Navigation إمكانية استخدام تطبيقات Jetpack Compose. يمكنكم التنقّل بين الدوال المركّبة مع الاستفادة من البنية الأساسية والميزات التي يوفّرها مكوّن Navigation.
للاطّلاع على أحدث مكتبة تنقّل في مرحلة ما قبل الإصدار تم إنشاؤها خصيصًا لـ Compose، يُرجى الرجوع إلى مستندات Navigation 3.
الإعداد
لاستخدام Compose، استخدِموا الاعتمادية التالية في ملف build.gradle الخاص بوحدة تطبيقكم:
Groovy
dependencies { def nav_version = "2.9.8" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.8" implementation("androidx.navigation:navigation-compose:$nav_version") }
البدء
عند تنفيذ ميزة التنقّل في تطبيق، يجب تنفيذ مضيف تنقّل ورسم بياني ووحدة تحكّم. لمزيد من المعلومات، يُرجى الاطّلاع على نظرة عامة على Navigation.
إنشاء عنصر `NavController`
للتعرّف على كيفية إنشاء عنصر NavController في Compose، يُرجى الاطّلاع على قسم Compose
في مقالة إنشاء وحدة تحكّم في التنقّل.
إنشاء عنصر `NavHost`
للتعرّف على كيفية إنشاء عنصر NavHost في Compose، يُرجى الاطّلاع على قسم Compose
في مقالة تصميم رسم بياني للتنقّل.
الانتقال إلى دالة مركّبة
للتعرّف على كيفية الانتقال إلى دالة مركّبة، يُرجى الاطّلاع على مقالة الانتقال إلى وجهة في مستندات البنية.
التنقّل باستخدام الوسيطات
للتعرّف على كيفية تمرير الوسيطات بين الوجهات المركّبة، يُرجى الاطّلاع على قسم 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 التي يمكن إنشاؤها باستخدام 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>
تربط ميزة Navigation تلقائيًا هذا الرابط العميق بالدالة المركّبة عندما يتم تفعيل الرابط العميق من خلال تطبيق آخر.
يمكن أيضًا استخدام هذه الروابط العميقة لإنشاء 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 مع التنقّل الأساسي، ولكن غالبًا ما تتضمّن التنسيقات التكيُّفية دوال مركّبة متخصّصة أخرى. بالنسبة إلى التنسيقات الأساسية لعرض على شكل قائمة مع تفاصيل واللوحة الداعمة، الشائعة في التصاميم التكيُّفية، استخدِموا
ListDetailPaneScaffold و SupportingPaneScaffold على التوالي.
لمزيد من المعلومات، يُرجى الاطّلاع على مقالة إنشاء تنسيقات تكيُّفية.
إمكانية التشغيل التفاعلي
إذا أردتم استخدام مكوّن Navigation مع Compose، يتوفّر لكم خياران:
- تحديد رسم بياني للتنقّل باستخدام مكوّن Navigation للقصاصات
- تحديد رسم بياني للتنقّل باستخدام
NavHostفي Compose باستخدام وجهات Compose لا يمكن فعل ذلك إلا إذا كانت جميع الشاشات في رسم بياني التنقّل دوال مركّبة.
لذلك، ننصح باستخدام مكوّن Navigation المستند إلى القصاصات للتطبيقات المختلطة التي تستخدم Compose وViews. ستحتوي القصاصات بعد ذلك على شاشات مستندة إلى View وشاشات Compose وشاشات تستخدم كلاً من Views وCompose. بعد أن يصبح محتوى كل قصاصة في Compose، تتمثل الخطوة التالية في ربط كل هذه الشاشات معًا باستخدام Navigation Compose وإزالة جميع القصاصات.
التنقّل من Compose باستخدام Navigation للقصاصات
لتغيير الوجهات داخل رمز Compose، يجب عرض الأحداث التي يمكن تمريرها إلى أي دالة مركّبة في التسلسل الهرمي وتفعيلها من خلالها:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
في القصاصة، يمكنكم إنشاء جسر بين Compose ومكوّن Navigation المستند إلى القصاصات من خلال العثور على NavController والانتقال إلى الوجهة:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
بدلاً من ذلك، يمكنكم تمرير NavController إلى التسلسل الهرمي لـ Compose.
ومع ذلك، فإنّ عرض الدوال يكون أكثر قابلية لإعادة الاستخدام والاختبار.
الاختبار
يمكنكم فصل رمز التنقّل عن الوجهات المركّبة لتتمكّنوا من اختبار كل دالة مركّبة على حدة، بشكل منفصل عن الدالة المركّبة NavHost.
هذا يعني أنّه يجب عدم تمرير navController مباشرةً إلى أي
دالة مركّبة، بل تمرير عمليات معاودة الاتصال بالتنقّل كمعلّمات. يسمح ذلك باختبار جميع الدوال المركّبة بشكل فردي، لأنّها لا تتطلّب مثيلاً لـ navController في الاختبارات.
إنّ مستوى التوجيه غير المباشر الذي توفّره تعبير lambda composable هو ما يسمح لكم بفصل رمز Navigation عن الدالة المركّبة نفسها. يعمل ذلك في اتجاهين:
- تمرير الوسيطات التي تم تحليلها فقط إلى الدالة المركّبة
- تمرير تعبيرات lambda التي يجب تفعيلها من خلال الدالة المركّبة للتنقّل، بدلاً من
NavControllerنفسه
على سبيل المثال، قد يكون للدالة المركّبة ProfileScreen التي تأخذ userId كإدخال وتسمح للمستخدمين بالانتقال إلى صفحة الملف الشخصي لصديق التوقيع التالي:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
بهذه الطريقة، تعمل الدالة المركّبة ProfileScreen بشكل مستقل عن Navigation، ما يسمح باختبارها بشكل مستقل. سيغلّف تعبير lambda composable الحد الأدنى من المنطق اللازم لسدّ الفجوة بين واجهات برمجة تطبيقات 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 وإجراءات التنقّل التي يتم تمريرها إلى الدوال المركّبة، بالإضافة إلى الدوال المركّبة الفردية للشاشة.
اختبار 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.
يمكنكم أيضًا استخدام navController للتحقّق من التأكيدات من خلال مقارنة المسار الحالي بالمسار المتوقّع، باستخدام currentBackStackEntry الخاص بـ navController:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
لمزيد من الإرشادات حول أساسيات اختبار Compose، يُرجى الاطّلاع على مقالة اختبار تنسيق Compose والدرس التطبيقي حول الترميز الاختبار في Jetpack Compose. لمزيد من المعلومات حول الاختبار المتقدّم لرمز التنقّل، يُرجى الانتقال إلى دليل اختبار التنقّل.
مزيد من المعلومات
لمزيد من المعلومات حول التنقّل في Jetpack، يُرجى الاطّلاع على مقالة البدء في استخدام مكوّن Navigation أو إجراء الدرس التطبيقي حول الترميز التنقّل في Jetpack Compose.
للتعرّف على كيفية تصميم ميزة التنقّل في تطبيقكم بحيث تتكيّف مع أحجام الشاشات المختلفة واتجاهاتها وعوامل شكلها، يُرجى الاطّلاع على مقالة التنقّل لواجهات المستخدم المتجاوبة.
للتعرّف على عملية تنفيذ أكثر تقدّمًا للتنقّل في Compose في تطبيق معيّن، بما في ذلك مفاهيم مثل الرسوم البيانية المضمّنة ودمج شريط التنقّل السفلي، يُرجى الاطّلاع على تطبيق Now in Android على GitHub.
نماذج
اقتراحات مخصصة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript
- التصميم المتعدد الأبعاد 2 في Compose
- نقل التنقّل في Jetpack إلى Navigation Compose
- مكان رفع الحالة