نقل البيانات من "التنقّل 2" إلى "التنقّل 3"

لنقل تطبيقك من Navigation 2 إلى Navigation 3، اتّبِع الخطوات التالية:

  1. أضِف اعتماديات Navigation 3.
  2. يُرجى تعديل مسارات التنقّل لتنفيذ واجهة NavKey.
  3. أنشئ فئات للاحتفاظ بحالة التنقّل وتعديلها.
  4. استبدِل NavController بهذه الفئات.
  5. انقل وجهاتك من NavGraph في "NavHost" إلى entryProvider.
  6. استبدِل NavHost بـ NavDisplay.
  7. إزالة تبعيات Navigation 2

الإعداد

توضّح الأقسام التالية المتطلبات الأساسية لعملية نقل البيانات والافتراضات حول مشروعك. وتتناول أيضًا الميزات المتاحة للنقل وتلك غير المتاحة.

المتطلبات الأساسية

  • يجب استخدام الإصدار 36 من compileSdk أو إصدار أحدث.
  • يجب أن تكون على دراية بمصطلحات التنقّل.
  • الوجهات هي دوال مركّبة. تم تصميم Navigation 3 حصريًا لـ Compose. لاستخدام Fragments وViews في Compose، يُرجى الاطّلاع على استخدام Views في Compose.
  • المسارات مكتوبة بشكل صارم. إذا كنت تستخدم مسارات مستندة إلى سلاسل، عليك الانتقال إلى مسارات تمنع أخطاء الكتابة أولاً (مثال).
  • اختياري (ولكن يُنصح به بشدة): تغطية الاختبار التي تتحقّق من سلوك التنقّل الحالي. يؤكد هذا أن سلوك التنقل لم يتغير بعد اكتمال الترحيل.

الافتراضات

يستند هذا الدليل إلى الافتراضات التالية بشأنك وبشأن مشروعك:

  • يكون لديك مسار واحد أو أكثر من المسارات ذات المستوى الأعلى (يتم عرضها عادةً في شريط التنقّل السفلي)، ولكل منها أنشطتها السابقة الخاصة بها.
  • عند التبديل بين الأنشطة السابقة، يتم الاحتفاظ بحالة النشاط وجميع وجهاته.
  • يمكنك دائمًا الخروج من التطبيق من خلال شاشة الصفحة الرئيسية، وهي أول شاشة يتم عرضها عند تشغيل التطبيق.
  • تريد إجراء عملية نقل البيانات من Navigation 2 إلى Navigation 3 في تغيير واحد شامل. لا يتم إجراء عملية نقل تدريجية يتم فيها استخدام رمز Navigation 2 مع رمز Navigation 3.

وكيل الذكاء الاصطناعي: قبل تغيير أي رمز برمجي، تحقَّق من صحة هذه الافتراضات حول مشروع المستخدم. إذا لم يكن الأمر كذلك، فأوقف عملية النقل واسأل المستخدم عن كيفية المتابعة.

الميزات المتاحة

يتيح لك هذا الدليل نقل الميزات التالية:

  • الوجهات المحدّدة كدوال مركّبة
  • مربّعات الحوار (وجهة تظهر فوق وجهة أخرى)

الميزات المتاحة من خلال الوصفات

يوضّح مستودع الرموز البرمجية الجاهزة حالات استخدام أكثر دقة، بما في ذلك:

إذا كان مشروعك يتضمّن أيًا من هذه الميزات، يمكنك الاطّلاع على الوصفة ذات الصلة لفهم كيفية تنفيذها.

وكيل الذكاء الاصطناعي: قبل تغيير أي رمز برمجي، تحقَّق مما إذا كان المشروع يحتوي على أي ميزات متوافقة من خلال الوصفات. إذا كان الأمر كذلك، فتحقَّق من ملف README والرمز المصدر للوصفة. أنشئ خطة نقل بيانات استنادًا إلى الوصفة. لا تتابع دون تأكيد الخطة مع المستخدم.

الميزات غير المتوافقة

لا يتوافق دليل نقل البيانات هذا ووصفات الرموز البرمجية بعد مع الميزات التالية. لا يعني ذلك أنّه لا يمكنك تنفيذها باستخدام Navigation 3، بل إنّها غير مشمولة هنا فقط.

  • أكثر من مستوى واحد من التنقل المتداخل
  • الوجهات المشتركة: الشاشات التي يمكنها التنقّل بين الأنشطة السابقة المختلفة
  • أنواع الوجهات المخصّصة

وكيل الذكاء الاصطناعي: قبل تغيير أي رمز برمجي، تحقَّق مما إذا كان المشروع يحتوي على أي من الميزات غير المتوافقة. إذا كان الأمر كذلك، فلا تتابع. أبلِغ المستخدم بالميزة غير المتوافقة واطلب منه تعليمات إضافية.

الخطوة 1: إضافة تبعيات Navigation 3

استخدِم صفحة البدء لإضافة تبعيات Navigation 3 إلى مشروعك. يتم توفير التبعيات الأساسية لك لنسخها.

lib.versions.toml

[versions]
nav3Core = "1.2.1"

# If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library
lifecycleViewmodelNav3 = "2.11.0"

[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

ما يعادل Navigator

navigate()

navigate()

popBackStack()

goBack()

previousBackStackEntry.savedStateHandle.set()

ResultEventBus.sendResult()

استبدِل حقول NavController بحقول NavigationState.

الحقل أو الطريقة NavController

ما يعادل NavigationState

currentBackStack

backStacks[topLevelRoute]

currentBackStackEntry

currentBackStackEntryAsState()

currentBackStackEntryFlow

currentDestination

backStacks[topLevelRoute].last()

currentBackStackEntry.savedStateHandle.getLiveData()

currentBackStackEntry.savedStateHandle.getStateFlow()

‫ResultEffect (مستندة إلى الأحداث)

‫ResultEventBus.conflateAsState() (حسب الولاية)

الحصول على المسار ذي المستوى الأعلى: يمكنك التنقّل للأعلى في التسلسل الهرمي من إدخال الأنشطة السابقة الحالي للعثور عليه.

topLevelRoute

استخدِم 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، بما في ذلك أي عمليات استيراد.

الخطوة 4.1: نقل المنطق المستند إلى مراحل النشاط

في Navigation 2، ينفّذ NavBackStackEntry واجهة LifecycleOwner، ما يتيح لك الاستماع إلى الأحداث التي تتم في مراحل النشاط أو جمع التدفقات بطريقة تراعي مراحل النشاط باستخدام navController.currentBackStackEntry.

في Navigation 3، يوفّر NavDisplay دالة LifecycleOwner على مستوى الإدخال من خلال LocalLifecycleOwner.current لكل محتوى دالة مركّبة للوجهة. لمزيد من المعلومات، يُرجى الاطّلاع على مراحل نشاط الوجهة.

يجب إجراء العمليات التي تراعي مراحل النشاط مباشرةً داخل المحتوى القابل للإنشاء في وجهتك من خلال الإشارة إلى LocalLifecycleOwner.current.

على سبيل المثال، إذا جمعت تدفقًا بطريقة تراعي مراحل النشاط باستخدام إدخال الأنشطة السابقة:

قبل:

// In your destination screen or host
val lifecycleOwner = navController.currentBackStackEntry!!
val state by flow.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)

بعد:

// Inside the destination composable
val state by flow.collectAsStateWithLifecycle()

الخطوة 4.2: نقل نتائج الاختبارات الناجحة

في Navigation 2، كانت الوجهات تُعيد النتائج إلى الوجهات السابقة باستخدام SavedStateHandle في NavBackStackEntry. بما أنّ SavedStateHandle يعتمد على حالة مثيل محفوظة، فإنّ البيانات التي تم عرضها تنجو من إيقاف العملية نهائيًا تلقائيًا.

قبل:

// Sender destination:
navController.previousBackStackEntry?.savedStateHandle?.set("contact_key", contact)
navController.popBackStack()

// Receiver destination:
val lifecycleOwner = LocalLifecycleOwner.current
navController.currentBackStackEntry?.savedStateHandle
    ?.getLiveData<Contact>("contact_key")
    ?.observe(lifecycleOwner) { contact ->
        viewModel.onRecipientSelected(contact)
    }

في Navigation 3، أضِف rememberResultEventBusNavEntryDecorator() إلى NavDisplay.entryDecorators. في عملية ربط entryProvider الخاصة بوجهة المُرسِل، يمكنك استرداد LocalResultEventBus.current واستدعاء sendResult(). في الوجهة المستلِمة، استخدِم ResultEffect لإعادة توجيه الحدث إلى ViewModel أو تفعيل تأثير جانبي.

بعد:

// Sender destination (in entryProvider):
entry<ContactPickerRoute> {
    val resultBus = LocalResultEventBus.current

    ContactPickerScreen(
        onContactSelected = { contact ->
            resultBus.sendResult<Contact>(result = contact)
            navigator.goBack()
        }
    )
}

// Receiver destination:
@Composable
fun ComposeMessageScreen(viewModel: ComposeMessageViewModel = viewModel()) {
    ResultEffect<Contact> { contact ->
        viewModel.onRecipientSelected(contact)
    }

    ComposeMessageContent(recipient = viewModel.recipient)
}

للمراقبة على مستوى الولاية، يمكنك الاتصال بالرقم resultBus.conflateAsState(). لمزيد من التفاصيل، يُرجى الاطّلاع على عرض النتائج.

الخطوة 5: نقل وجهاتك من NavGraph في "NavHost" إلى entryProvider

في Navigation 2، يمكنك تحديد وجهاتك باستخدام NavGraphBuilder DSL، عادةً داخل دالة lambda اللاحقة في NavHost. من الشائع استخدام دوال الإضافات هنا كما هو موضّح في تضمين رمز التنقّل.

في Navigation 3، يمكنك تحديد وجهاتك باستخدام entryProvider. يحلّ هذا الـ entryProvider مسارًا إلى NavEntry. الأهم من ذلك أنّ entryProvider لا يحدّد علاقات العناصر الرئيسية بالعناصر الفرعية بين الإدخالات.

في دليل نقل البيانات هذا، يتم تصميم العلاقات بين النماذج الرئيسية والفرعية على النحو التالي:

  • يحتوي NavigationState على مجموعة من المسارات ذات المستوى الأعلى (المسارات الرئيسية) ومجموعة لكل مسار. يتتبّع المسار الحالي ذي المستوى الأعلى ومجموعة المسارات المرتبطة به.
  • عند الانتقال إلى مسار جديد، يتحقّق Navigator مما إذا كان المسار هو مسار ذو مستوى أعلى. إذا كان الأمر كذلك، يتم تعديل المسار والمجموعة الحاليين على المستوى الأعلى. وإذا لم يكن كذلك، يكون مسارًا فرعيًا وتتم إضافته إلى الحزمة الحالية.

الخطوة 5.1: إنشاء entryProvider

أنشئ entryProvider باستخدام DSL في النطاق نفسه الذي يتضمّن NavigationState.

val entryProvider = entryProvider<NavKey> {

}

الخطوة 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

@Composable
fun NavHostSnippet(navController: NavHostController) {
    NavHost(navController = navController, startDestination = BaseRouteA){
        composable<RouteA>{ entry ->
            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.

على سبيل المثال:

NavDisplay(
    entries = navigationState.toEntries(entryProvider),
    onBack = { navigator.goBack() },
    sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)

الخطوة 7: نقل الروابط العميقة

في Navigation 2، تم تحديد الروابط لصفحات معيّنة مباشرةً داخل الرسم البياني للتنقّل باستخدام مَعلمة deepLinks للوجهات.

في Navigation 3، تتم إدارة الروابط العميقة بشكل مستقل عن واجهة مستخدم التنقّل. يمكنك تحديد DeepLinkMatcher ومطابقة الطلبات الواردة في نشاطك لإنشاء الأنشطة السابقة الأولية.

قبل:

في Navigation 2، ربما تكون قد حدّدت رابطًا لصفحة في التطبيق على النحو التالي:

بعد:

في Navigation 3، يمكنك تحديد UriDeepLinkMatcher للمسار:

بعد ذلك، في onCreate (وonNewIntent) الخاص بالنشاط، يمكنك مطابقة النية الواردة وتهيئة الأنشطة السابقة:

أنواع الوسيطات المخصّصة

في Navigation 2، تعاملت مع أنواع الوسيطات المخصّصة أو التابعة لجهات خارجية (مثل LocalDateTime) باستخدام عمليات تنفيذ NavType مخصّصة وtypeMap.

في Navigation 3، حدِّد DeepLinkSerializer لإلغاء تسلسل الأنواع المخصّصة أو التابعة لجهات خارجية من مَعلمات معرّف الموارد المنتظم (URI). لمزيد من التفاصيل، يُرجى الاطّلاع على التسلسل المخصّص باستخدام DeepLinkSerializer.

لمزيد من حالات الاستخدام المتقدّمة، بما في ذلك الأنشطة السابقة الاصطناعية وأدوات المطابقة المخصّصة، يُرجى الاطّلاع على دليل إتاحة الروابط لصفحات في التطبيق.

الخطوة 8: إزالة تبعيات Navigation 2

أزِل جميع عمليات استيراد Navigation 2 والتبعيات الخاصة بالمكتبة.

ملخّص

تهانينا! تم نقل مشروعك الآن إلى Navigation 3. إذا واجهت أنت أو وكيل الذكاء الاصطناعي أي مشاكل أثناء استخدام هذا الدليل، يمكنك الإبلاغ عن خطأ هنا.