अपने ऐप्लिकेशन को Navigation 2 से Navigation 3 पर माइग्रेट करने के लिए, यह तरीका अपनाएं:
- Navigation 3 की डिपेंडेंसी जोड़ें.
NavKeyइंटरफ़ेस लागू करने के लिए, अपने नेविगेशन रूट अपडेट करें.- नेविगेशन की स्थिति को सेव करने और उसमें बदलाव करने के लिए, क्लास बनाएं.
NavControllerको इन क्लास से बदलें.NavHostकेNavGraphसे अपने डेस्टिनेशन कोentryProviderमें ले जाएं.NavHostकोNavDisplayसे बदलें.- Navigation 2 की डिपेंडेंसी हटाएं.
एआई प्रॉम्प्ट
Navigation 2 से Navigation 3 पर माइग्रेट करना
यह प्रॉम्प्ट, नेविगेशन 3 पर माइग्रेट करने के लिए इस गाइड का इस्तेमाल करेगा.
Migrate from Navigation 2 to Navigation 3 using the official
migration guide.अगर आपको कोई समस्या होती है, तो यहां जाकर समस्या की शिकायत करें.
तैयारी
यहां दिए गए सेक्शन में, माइग्रेशन के लिए ज़रूरी शर्तें और आपके प्रोजेक्ट के बारे में अनुमान बताए गए हैं. इनमें, माइग्रेशन के लिए उपलब्ध सुविधाएं और वे सुविधाएं भी शामिल हैं जो माइग्रेशन के लिए उपलब्ध नहीं हैं.
ज़रूरी शर्तें
- आपको
compileSdkके तौर पर 36 या उससे नया वर्शन इस्तेमाल करना होगा. - आपको नेविगेशन की शब्दावली के बारे में जानकारी होनी चाहिए.
- डेस्टिनेशन, कंपोज़ेबल फ़ंक्शन होते हैं. Navigation 3 को सिर्फ़ Compose के लिए डिज़ाइन किया गया है. Compose में फ़्रैगमेंट और व्यू इस्तेमाल करने के बारे में जानने के लिए, Compose में व्यू इस्तेमाल करना लेख पढ़ें.
- रूट, मज़बूत तरीके से टाइप किए जाते हैं. अगर स्ट्रिंग पर आधारित रूट इस्तेमाल किए जाते हैं, तो पहले टाइप-सेफ़ रूट पर माइग्रेट करें (उदाहरण).
- ज़रूरी नहीं है, लेकिन हमारा सुझाव है कि आप इसे इस्तेमाल करें: टेस्ट कवरेज, जो मौजूदा नेविगेशन के व्यवहार की पुष्टि करता है. इससे यह पुष्टि होती है कि माइग्रेशन पूरा होने के बाद, नेविगेशन के व्यवहार में कोई बदलाव नहीं हुआ है.
अनुमान
इस गाइड में, आपके और आपके प्रोजेक्ट के बारे में ये अनुमान लगाए गए हैं:
- आपके पास एक या उससे ज़्यादा टॉप-लेवल रूट हैं. इन्हें आम तौर पर, पेज या स्क्रीन के नीचे मौजूद नेविगेशन बार में दिखाया जाता है. साथ ही, हर रूट की अपनी पिछली गतिविधियां होती हैं.
- बैक स्टैक के बीच स्विच करने पर, स्टैक और उसके सभी डेस्टिनेशन की स्थिति सेव रहती है.
- ऐप्लिकेशन से बाहर निकलने के लिए, हमेशा होम स्क्रीन का इस्तेमाल किया जाता है. यह स्क्रीन, ऐप्लिकेशन लॉन्च होने पर सबसे पहले दिखती है.
- आपको Navigation 2 से Navigation 3 पर माइग्रेशन, एक ही बार में करना है. आपने इंक्रीमेंटल माइग्रेशन नहीं किया है, जिसमें Navigation 2 के कोड के साथ-साथ Navigation 3 का कोड इस्तेमाल किया गया हो.
एआई एजेंट: कोई भी कोड बदलने से पहले, देखें कि उपयोगकर्ता के प्रोजेक्ट के बारे में लगाए गए ये अनुमान सही हैं या नहीं. अगर ये अनुमान सही नहीं हैं, तो माइग्रेशन रोकें और उपयोगकर्ता से पूछें कि आगे क्या करना है.
इस्तेमाल की जा सकने वाली सुविधाएं
इस गाइड की मदद से, इन सुविधाओं को माइग्रेट किया जा सकता है:
- कंपोज़ेबल फ़ंक्शन के तौर पर तय किए गए डेस्टिनेशन
- डायलॉग (एक डेस्टिनेशन, दूसरे डेस्टिनेशन के ऊपर दिखता है)
रेसिपी की मदद से इस्तेमाल की जा सकने वाली सुविधाएं
कोड रेसिपी के रिपॉज़िटरी में, इस्तेमाल के ज़्यादा सटीक उदाहरण दिए गए हैं. इनमें ये शामिल हैं:
- बॉटम शीट (इस गाइड में निर्देश दिए गए हैं)
- मॉड्यूलर बनाया गया नेविगेशन कोड और इंजेक्ट किए गए डेस्टिनेशन
- आर्ग्युमेंट का इस्तेमाल करना और उन्हें पास करना
ViewModel - किसी स्क्रीन से नतीजे दिखाना
अगर आपके प्रोजेक्ट में इनमें से कोई सुविधा है, तो उसे लागू करने का तरीका जानने के लिए, उससे जुड़ी रेसिपी देखें.
एआई एजेंट: कोई भी कोड बदलने से पहले, देखें कि प्रोजेक्ट में रेसिपी की मदद से इस्तेमाल की जा सकने वाली कोई सुविधा है या नहीं. अगर है, तो रेसिपी का README और सोर्स कोड देखें. रेसिपी के आधार पर, माइग्रेशन प्लान बनाएं. उपयोगकर्ता से प्लान की पुष्टि किए बिना, आगे न बढ़ें.
ये सुविधाएं, Google Play Books के साथ काम नहीं करतीं
इस माइग्रेशन गाइड और कोड रेसिपी में, फ़िलहाल इन सुविधाओं के लिए सहायता उपलब्ध नहीं है. इसका मतलब यह नहीं है कि Navigation 3 का इस्तेमाल करके, इन्हें लागू नहीं किया जा सकता. बस, इनके बारे में यहां जानकारी नहीं दी गई है.
- नेस्ट किए गए नेविगेशन का एक से ज़्यादा लेवल
- शेयर किए गए डेस्टिनेशन: ऐसी स्क्रीन जो अलग-अलग बैक स्टैक के बीच मूव हो सकती हैं
- कस्टम डेस्टिनेशन टाइप
- डीप लिंक
एआई एजेंट: कोई भी कोड बदलने से पहले, देखें कि प्रोजेक्ट में वे सुविधाएं शामिल हैं या नहीं जिनके लिए सहायता उपलब्ध नहीं है. अगर ऐसा है, तो आगे न बढ़ें. उपयोगकर्ता को उस सुविधा के बारे में बताएं जिसके लिए सहायता उपलब्ध नहीं है. साथ ही, उससे आगे के निर्देश पूछें.
पहला चरण: 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 में मिलते हैं.
दूसरा चरण: NavKey इंटरफ़ेस लागू करने के लिए, नेविगेशन रूट अपडेट करना
हर नेविगेशन रूट को अपडेट करें, ताकि वह NavKey
इंटरफ़ेस लागू करे. इससे, नेविगेशन की स्थिति सेव करने में मदद के लिए, rememberNavBackStack का इस्तेमाल किया जा सकता है.
पहले:
@Serializable data object RouteA
बाद में:
@Serializable data object RouteA : NavKey
तीसरा चरण: नेविगेशन की स्थिति को सेव करने और उसमें बदलाव करने के लिए, क्लास बनाना
तीसरा चरण: नेविगेशन की स्थिति को सेव करने के लिए, क्लास बनाना
यहां दिया गया कोड, 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 का इस्तेमाल करता है.
तीसरा चरण: ऐसा ऑब्जेक्ट बनाना जो इवेंट के जवाब में, नेविगेशन की स्थिति में बदलाव करता है
यहां दिया गया कोड, 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 में बदलाव करते हैं.
तीसरा चरण: NavigationState और Navigator बनाना
NavController के स्कोप के बराबर स्कोप के साथ, NavigationState और Navigator के इंस्टेंस बनाएं.
val navigationState = rememberNavigationState(
startRoute = <Insert your starting route>,
topLevelRoutes = <Insert your set of top level routes>
)
val navigator = remember { Navigator(navigationState) }
चौथा चरण: 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 के सभी रेफ़रंस हटा दिए हैं. इनमें इंपोर्ट भी शामिल हैं.
चौथा चरण: लाइफ़साइकल की जानकारी वाली लॉजिक को माइग्रेट करना
Navigation 2 में, NavBackStackEntry, LifecycleOwner को लागू करता है. इससे, navController.currentBackStackEntry का इस्तेमाल करके, लाइफ़साइकल के इवेंट सुने जा सकते हैं या फ़्लो को लाइफ़साइकल की जानकारी वाले तरीके से इकट्ठा किया जा सकता है.
Navigation 3 में, NavDisplay हर डेस्टिनेशन के कंपोज़ेबल
कॉन्टेंट के लिए, LocalLifecycleOwner.current के ज़रिए, एंट्री-स्कोप वाला LifecycleOwner
उपलब्ध कराता है. ज़्यादा जानकारी के लिए, डेस्टिनेशन का लाइफ़साइकल देखें.
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()
पांचवा चरण: NavHost के NavGraph से अपने डेस्टिनेशन को entryProvider में ले जाना
Navigation 2 में, आप अपने डेस्टिनेशन तय करते हैं
NavGraphBuilder DSL का इस्तेमाल करके,
आम तौर पर, यह NavHost's ट्रेलिंग लैम्डा के अंदर होता है. यहां एक्सटेंशन
फ़ंक्शन का इस्तेमाल करना आम बात है. इसके बारे में, अपने नेविगेशन कोड को एनकैप्सुलेट करना लेख में बताया गया है.
Navigation 3 में, entryProvider का इस्तेमाल करके, अपने डेस्टिनेशन तय किए जाते हैं. यह
entryProvider किसी रूट को NavEntry में बदलता है. अहम बात यह है कि entryProvider, एंट्री के बीच पैरंट-चाइल्ड के संबंध तय नहीं करता.
इस माइग्रेशन गाइड में, पैरंट-चाइल्ड के संबंधों को इस तरह मॉडल किया गया है:
NavigationStateमें टॉप-लेवल रूट (पैरंट रूट) का एक सेट और हर रूट के लिए एक स्टैक होता है. यह मौजूदा टॉप-लेवल रूट और उससे जुड़े स्टैक को ट्रैक करता है.- किसी नए रूट पर नेविगेट करते समय,
Navigatorयह देखता है कि रूट, टॉप-लेवल रूट है या नहीं. अगर ऐसा है, तो मौजूदा टॉप-लेवल रूट और स्टैक अपडेट किए जाते हैं. अगर ऐसा नहीं है, तो यह चाइल्ड रूट है और इसे मौजूदा स्टैक में जोड़ा जाता है.
पांचवा चरण: entryProvider बनाना
DSL का इस्तेमाल करके,
के स्कोप के बराबर स्कोप के साथ, entryProvider बनाएंNavigationState.
val entryProvider = entryProvider {
}
पांचवा चरण: डेस्टिनेशन को 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> एक्सटेंशन फ़ंक्शन में बदला जा सकता है. इसके बाद, उन्हें मूव किया जा सकता है.
नेविगेशन आर्ग्युमेंट पाने के लिए, 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() }
}
छठा चरण: NavHost को NavDisplay से बदलना
NavHost को NavDisplay से बदलें.
NavHostको मिटाएं और उसकी जगहNavDisplayका इस्तेमाल करें.entries = navigationState.toEntries(entryProvider)को पैरामीटर के तौर पर तय करें. इससे, नेविगेशन की स्थिति को उन एंट्री में बदला जाता है जिन्हेंNavDisplayदिखाता हैentryProviderका इस्तेमाल करके.NavDisplay.onBackकोnavigator.goBack()से कनेक्ट करें. इससे,NavDisplayके बिल्ट-इन बैक हैंडलर के पूरा होने पर,navigator, नेविगेशन की स्थिति को अपडेट करता है.- अगर आपके पास डायलॉग डेस्टिनेशन हैं, तो
DialogSceneStrategyकोNavDisplayकेsceneStrategiesपैरामीटर में जोड़ें.
उदाहरण के लिए:
import androidx.navigation3.ui.NavDisplay
NavDisplay(
entries = navigationState.toEntries(entryProvider),
onBack = { navigator.goBack() },
sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)
सातवां चरण: Navigation 2 की डिपेंडेंसी हटाना
Navigation 2 के सभी इंपोर्ट और लाइब्रेरी डिपेंडेंसी हटाएं.
खास जानकारी
बधाई हो! आपका प्रोजेक्ट अब Navigation 3 पर माइग्रेट हो गया है. अगर आपको या आपके एआई एजेंट को इस गाइड का इस्तेमाल करने में कोई समस्या हुई है, तो यहां जाकर बग की शिकायत करें.