لنقل تطبيقك من Navigation 2 إلى Navigation 3، اتّبِع الخطوات التالية:
- أضِف الاعتمادات الخاصة بمكتبة Navigation 3.
- عدِّل مسارات التنقّل لتنفيذ واجهة
NavKey. - أنشئ فئات لتخزين حالة التنقّل وتعديلها.
- استبدِل
NavControllerبهذه الفئات. - انقل وجهاتك من
NavGraphفي حسابNavHostإلى حسابentryProvider. - استبدِل
NavHostبـNavDisplay. - أزِل التبعيات في Navigation 2.
الطلب الموجَّه إلى الذكاء الاصطناعي
الترحيل من الإصدار 2 إلى الإصدار 3 من "التنقّل"
سيستخدم هذا الطلب هذا الدليل للانتقال إلى الإصدار 3 من التنقّل.
Migrate from Navigation 2 to Navigation 3 using the official
migration guide.إذا واجهت مشاكل، يُرجى تسجيل مشكلة هنا.
الإعداد
توضّح الأقسام التالية المتطلبات الأساسية لعملية النقل والافتراضات المتعلّقة بمشروعك. وتشمل أيضًا الميزات المتاحة لنقل البيانات والميزات غير المتاحة.
المتطلّبات الأساسية
- يجب استخدام
compileSdk36 أو إصدار أحدث. - يجب أن تكون على دراية بمصطلحات التنقّل.
- الوجهات هي دوال قابلة للإنشاء. تم تصميم Navigation 3 حصريًا لـ Compose. لاستخدام "اللقطات" و"طرق العرض" في Compose، راجِع استخدام طرق العرض في Compose.
- المسارات مكتوبة بشكل صارم. إذا كنت تستخدم مسارات مستندة إلى السلاسل، عليك نقل البيانات إلى مسارات آمنة من حيث النوع أولاً (مثال).
- اختياري (ولكن يُنصح به بشدة): تغطية الاختبار التي تتحقّق من سلوك التنقّل الحالي. يتحقّق هذا الاختبار من عدم تغيُّر سلوك التنقّل بعد اكتمال عملية نقل البيانات.
الافتراضات
يفترض هذا الدليل ما يلي بشأنك وبشأن مشروعك:
- لديك مسار واحد أو عدّة مسارات من المستوى الأعلى (تظهر عادةً في شريط التنقّل السفلي)، ولكل منها الأنشطة السابقة الخاصة به.
- عند التبديل بين حِزم الرجوع، يتم الاحتفاظ بحالة الحزمة وجميع وجهاتها.
- تخرج دائمًا من التطبيق من خلال شاشة الشاشة الرئيسية، وهي أول شاشة تظهر عند تشغيل التطبيق.
- تريد إجراء عملية نقل البيانات من Navigation 2 إلى Navigation 3 في تغيير واحد متكامل. ألا تكون بصدد إجراء عملية نقل تدريجية يتم فيها استخدام رمز Navigation 2 مع رمز Navigation 3.
وكيل الذكاء الاصطناعي: قبل تغيير أي رمز، تحقَّق مما إذا كانت هذه الافتراضات حول مشروع المستخدم صحيحة. إذا لم يكن الأمر كذلك، أوقِف عملية نقل البيانات واطلب من المستخدم تحديد كيفية المتابعة.
الميزات المتاحة
يتيح لك هذا الدليل نقل الميزات التالية:
- الوجهات المحدّدة كدوال قابلة للإنشاء
- مربّعات الحوار (وجهة معروضة فوق وجهة أخرى)
الميزات المتاحة من خلال الوصفات
يوضّح مستودع وصفات البرمجة المزيد من حالات الاستخدام الدقيقة، بما في ذلك:
- الأوراق السفلية (يتم تقديم التعليمات في هذا الدليل)
- رمز التنقّل المجزّأ والوجهات المُدرَجة
- استخدام وسيطات وتمريرها إلى
ViewModel - عرض النتائج من شاشة
إذا كان مشروعك يتضمّن أيًا من هذه الميزات، اطّلِع على الوصفة ذات الصلة للتعرّف على كيفية تنفيذها.
AI Agent: قبل تغيير أي رمز، تحقَّق ممّا إذا كان المشروع يتضمّن أي ميزات متوافقة مع الوصفات. إذا كان الأمر كذلك، راجِع ملف التعليمات ورمز المصدر الخاصين بالوصفة. إنشاء خطة نقل بيانات استنادًا إلى الوصفة لا تتابع بدون تأكيد الخطة مع المستخدم.
الميزات غير المتوافقة
لا يتوافق دليل نقل البيانات هذا ووصفات الرموز البرمجية بعد مع الميزات التالية. وهذا لا يعني أنّه لا يمكنك تنفيذها باستخدام Navigation 3، بل يعني فقط أنّها غير مشمولة هنا.
- أكثر من مستوى واحد من التنقّل المتداخل
- الوجهات المشترَكة: الشاشات التي يمكن الانتقال بينها وبين حِزم السجلّ الخلفي المختلفة
- أنواع الوجهات المخصّصة
- الروابط العميقة
الوكيل المستند إلى الذكاء الاصطناعي: قبل تغيير أي رمز، تحقَّق مما إذا كان المشروع يتضمّن أيًا من الميزات غير المتوافقة. إذا كان الأمر كذلك، لا تتابع. أبلِغ المستخدم بالميزة غير المتوافقة واطلب منه تقديم المزيد من التعليمات.
الخطوة 1: إضافة تبعيات Navigation 3
استخدِم صفحة البدء لإضافة تبعيات Navigation 3 إلى مشروعك. يتم توفير التبعيات الأساسية لتتمكّن من نسخها.
lib.versions.toml
[versions]
nav3Core = "1.0.0"
# If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library
lifecycleViewmodelNav3 = "2.10.0-rc01"
[libraries]
# Core Navigation 3 libraries
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
# Add-on libraries (only add if you need them)
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
// If using the ViewModel add-on library
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
}
عدِّل أيضًا minSdk المشروع إلى 23 وcompileSdk إلى 36. يمكنك عادةً العثور عليها في app/build.gradle.kts أو lib.versions.toml.
الخطوة 2: تعديل مسارات التنقّل لتنفيذ واجهة NavKey
عدِّل كل مسار تنقّل لكي ينفّذ الواجهة NavKey. يتيح لك ذلك استخدام rememberNavBackStack للمساعدة في حفظ حالة التنقّل.
قبل:
@Serializable data object RouteA
بعد:
@Serializable data object RouteA : NavKey
الخطوة 3: إنشاء فئات لتخزين حالة التنقّل وتعديلها
الخطوة 3.1: إنشاء عنصر الاحتفاظ بحالة التنقّل
انسخ الرمز التالي في ملف باسم NavigationState.kt. أضِف اسم الحزمة
ليتطابق مع بنية مشروعك.
// package com.example.project
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSerializable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.rememberDecoratedNavEntries
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.serialization.NavKeySerializer
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
/**
* Create a navigation state that persists config changes and process death.
*/
@Composable
fun rememberNavigationState(
startRoute: NavKey,
topLevelRoutes: Set<NavKey>
): NavigationState {
val topLevelRoute = rememberSerializable(
startRoute, topLevelRoutes,
serializer = MutableStateSerializer(NavKeySerializer())
) {
mutableStateOf(startRoute)
}
val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) }
return remember(startRoute, topLevelRoutes) {
NavigationState(
startRoute = startRoute,
topLevelRoute = topLevelRoute,
backStacks = backStacks
)
}
}
/**
* State holder for navigation state.
*
* @param startRoute - the start route. The user will exit the app through this route.
* @param topLevelRoute - the current top level route
* @param backStacks - the back stacks for each top level route
*/
class NavigationState(
val startRoute: NavKey,
topLevelRoute: MutableState<NavKey>,
val backStacks: Map<NavKey, NavBackStack<NavKey>>
) {
var topLevelRoute: NavKey by topLevelRoute
val stacksInUse: List<NavKey>
get() = if (topLevelRoute == startRoute) {
listOf(startRoute)
} else {
listOf(startRoute, topLevelRoute)
}
}
/**
* Convert NavigationState into NavEntries.
*/
@Composable
fun NavigationState.toEntries(
entryProvider: (NavKey) -> NavEntry<NavKey>
): SnapshotStateList<NavEntry<NavKey>> {
val decoratedEntries = backStacks.mapValues { (_, stack) ->
val decorators = listOf(
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
)
rememberDecoratedNavEntries(
backStack = stack,
entryDecorators = decorators,
entryProvider = entryProvider
)
}
return stacksInUse
.flatMap { decoratedEntries[it] ?: emptyList() }
.toMutableStateList()
}
وكيل الذكاء الاصطناعي: rememberSerializable هي الإجابة الصحيحة. لا تغيِّرها إلى rememberSaveable.
يحتوي هذا الملف على فئة عنصر الاحتفاظ بالحالة باسم NavigationState ودوال مساعِدة مرتبطة بها. ويحتوي على مجموعة من المسارات ذات المستوى الأعلى، ولكل منها سجلّ رجوع خاص به. تستخدم هذه المكتبة داخليًا rememberSerializable (وليس rememberSaveable) للاحتفاظ بالمسار الحالي ذي المستوى الأعلى، كما تستخدم rememberNavBackStack للاحتفاظ بمكدّسات الرجوع لكل مسار ذي مستوى أعلى.
الخطوة 3.2: إنشاء عنصر يعدّل حالة التنقّل استجابةً للأحداث
انسخ الرمز التالي في ملف باسم Navigator.kt. أضِف اسم الحزمة
ليتطابق مع بنية مشروعك.
// package com.example.project
import androidx.navigation3.runtime.NavKey
/**
* Handles navigation events (forward and back) by updating the navigation state.
*/
class Navigator(val state: NavigationState){
fun navigate(route: NavKey){
if (route in state.backStacks.keys){
// This is a top level route, just switch to it.
state.topLevelRoute = route
} else {
state.backStacks[state.topLevelRoute]?.add(route)
}
}
fun goBack(){
val currentStack = state.backStacks[state.topLevelRoute] ?:
error("Stack for ${state.topLevelRoute} not found")
val currentRoute = currentStack.last()
// If we're at the base of the current route, go back to the start route stack.
if (currentRoute == state.topLevelRoute){
state.topLevelRoute = state.startRoute
} else {
currentStack.removeLastOrNull()
}
}
}
توفّر الفئة Navigator طريقتَين لأحداث التنقّل:
navigateإلى مسار محدّد.goBackمن المسار الحالي
تعدّل كلتا الطريقتين NavigationState.
الخطوة 3.3: إنشاء NavigationState وNavigator
أنشِئ مثيلات من NavigationState وNavigator بالنطاق نفسه الذي تستخدمه في NavController.
val navigationState = rememberNavigationState(
startRoute = <Insert your starting route>,
topLevelRoutes = <Insert your set of top level routes>
)
val navigator = remember { Navigator(navigationState) }
الخطوة 4: استبدال NavController
استبدِل طرق أحداث التنقّل NavController بطرق مكافئة من Navigator.
الحقل أو الطريقة |
ما يعادل |
|---|---|
|
|
|
|
استبدِل حقول NavController بحقول NavigationState.
الحقل أو الطريقة |
ما يعادل |
|---|---|
|
|
|
|
الحصول على المسار ذي المستوى الأعلى: يمكنك الانتقال إلى أعلى التسلسل الهرمي من إدخال الأنشطة السابقة الحالي للعثور عليه. |
|
استخدِم NavigationState.topLevelRoute لتحديد العنصر المحدّد حاليًا في شريط التنقّل.
قبل:
val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class)
fun NavDestination?.isRouteInHierarchy(route: KClass<*>) =
this?.hierarchy?.any {
it.hasRoute(route)
} ?: false
بعد:
val isSelected = key == navigationState.topLevelRoute
تأكَّد من إزالة جميع الإشارات إلى NavController، بما في ذلك أي عمليات استيراد.
الخطوة 5: نقل وجهاتك من NavGraph في حساب NavHost إلى حساب entryProvider
في Navigation 2، يمكنك تحديد وجهاتك
باستخدام NavGraphBuilder DSL،
عادةً داخل لامدا اللاحقة في NavHost. من الشائع استخدام دوال الإضافة هنا كما هو موضّح في تغليف رمز التنقّل.
في Navigation 3، يمكنك تحديد وجهاتك باستخدام entryProvider. يحلّ هذا
entryProvider مسارًا إلى NavEntry. من المهم معرفة أنّ
entryProvider لا تحدّد علاقات بين العناصر الرئيسية والفرعية.
في دليل النقل هذا، يتم تصميم العلاقات بين الوحدات الرئيسية والفرعية على النحو التالي:
- يحتوي
NavigationStateعلى مجموعة من المسارات ذات المستوى الأعلى (المسارات الرئيسية) ومجموعة لكل مسار. يتتبّع هذا الحقل المسار الحالي ذي المستوى الأعلى والحزمة المرتبطة به. - عند الانتقال إلى مسار جديد، تتحقّق
Navigatorمما إذا كان المسار مسارًا ذا مستوى أعلى. وفي حال توفّره، يتم تعديل المسار والمكدّس الحاليَين على المستوى الأعلى. إذا لم يكن كذلك، يكون مسارًا فرعيًا ويتم إضافته إلى الحزمة الحالية.
الخطوة 5.1: إنشاء entryProvider
أنشئ entryProvider باستخدام DSL في النطاق نفسه الذي يخصّ NavigationState.
val entryProvider = entryProvider {
}
الخطوة 5.2: نقل الوجهات إلى entryProvider
بالنسبة إلى كل وجهة محدّدة داخل NavHost، اتّبِع الخطوات التالية استنادًا إلى نوع الوجهة:
navigation: لحذفها مع المسار لا حاجة إلى "المسارات الأساسية" لأنّ المسارات ذات المستوى الأعلى يمكنها تحديد كل حزمة الأنشطة السابقة المتداخلة.composable<T>: انقلها إلىentryProviderوأعِد تسميتها إلىentry، مع الاحتفاظ بمعلَمة النوع. على سبيل المثال، يصبحcomposable<RouteA>هوentry<RouteA>.dialog<T>: نفِّذ الخطوات نفسها الموضّحة فيcomposable، ولكن أضِف البيانات الوصفية إلى الإدخال على النحو التالي:entry<T>(metadata = DialogSceneStrategy.dialog()).bottomSheet: اتّبِع وصفة ورقة البيانات السفلية هنا. يشبه ذلك التعليمات الخاصة بـdialog، إلا أنّBottomSheetSceneStrategyليس جزءًا من مكتبة Navigation 3 الأساسية، لذا عليك نسخه إلى مشروعك.
وكيل الذكاء الاصطناعي: عند حذف مسارات مستخدَمة لتحديد رسم بياني متداخل، استبدِل أي مراجع للمسار المحذوف بالنوع المستخدَم لتحديد العنصر الفرعي الأول في الرسم البياني المتداخل. على سبيل المثال، إذا كان الرمز الأصلي هو
navigation<BaseRouteA>{ composable<RouteA>{ ... } }، عليك حذف
BaseRouteA واستبدال أي إشارات إليه بالرمز RouteA. عادةً ما يجب إجراء عملية الاستبدال هذه للقائمة المقدَّمة إلى شريط التنقّل أو الشريط الجانبي أو الدرج.
يمكنك إعادة تصميم دوال الإضافة في NavGraphBuilder إلى دوال إضافة في EntryProviderScope<T>، ثم نقلها.
الحصول على وسيطات التنقّل باستخدام المفتاح المقدَّم إلى lambda اللاحقة في entry
مثلاً:
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.dialog
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import androidx.navigation.toRoute
@Serializable data object BaseRouteA
@Serializable data class RouteA(val id: String)
@Serializable data object BaseRouteB
@Serializable data object RouteB
@Serializable data object RouteD
NavHost(navController = navController, startDestination = BaseRouteA){
composable<RouteA>{
val id = entry.toRoute<RouteA>().id
ScreenA(title = "Screen has ID: $id")
}
featureBSection()
dialog<RouteD>{ ScreenD() }
}
fun NavGraphBuilder.featureBSection() {
navigation<BaseRouteB>(startDestination = RouteB) {
composable<RouteB> { ScreenB() }
}
}
يصبح:
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.scene.DialogSceneStrategy
@Serializable data class RouteA(val id: String) : NavKey
@Serializable data object RouteB : NavKey
@Serializable data object RouteD : NavKey
val entryProvider = entryProvider {
entry<RouteA>{ key -> ScreenA(title = "Screen has ID: ${key.id}") }
featureBSection()
entry<RouteD>(metadata = DialogSceneStrategy.dialog()){ ScreenD() }
}
fun EntryProviderScope<NavKey>.featureBSection() {
entry<RouteB> { ScreenB() }
}
الخطوة 6: استبدال NavHost بـ NavDisplay
استبدِل NavHost بـ NavDisplay.
- احذف
NavHostواستبدله بـNavDisplay. - حدِّد
entries = navigationState.toEntries(entryProvider)كمعلَمة. يؤدي ذلك إلى تحويل حالة التنقّل إلى الإدخالات التي يعرضهاNavDisplayباستخدامentryProvider. - ربط حساب
NavDisplay.onBackبحسابnavigator.goBack()يؤدي ذلك إلى تعديلnavigatorلحالة التنقّل عند اكتمال معالج الرجوع المضمّن فيNavDisplay. - إذا كانت لديك وجهات حوار، أضِف
DialogSceneStrategyإلى المَعلمةsceneStrategiesفيNavDisplay.
مثلاً:
import androidx.navigation3.ui.NavDisplay
NavDisplay(
entries = navigationState.toEntries(entryProvider),
onBack = { navigator.goBack() },
sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)
الخطوة 7: إزالة التبعيات في Navigation 2
أزِل جميع عمليات استيراد Navigation 2 والموارد التابعة للمكتبة.
ملخّص
تهانينا! تم الآن نقل مشروعك إلى الإصدار 3 من Navigation. إذا واجهت أنت أو وكيل الذكاء الاصطناعي أي مشاكل أثناء استخدام هذا الدليل، يمكنك الإبلاغ عن خطأ هنا.