लिखने की सुविधा के साथ नेविगेशन

नेविगेशन कॉम्पोनेंट, Jetpack Compose ऐप्लिकेशन के साथ काम करता है. नेविगेशन कॉम्पोनेंट के इन्फ़्रास्ट्रक्चर और सुविधाओं का इस्तेमाल करते हुए, कंपोज़ेबल के बीच नेविगेट किया जा सकता है.

खास तौर पर Compose के लिए बनाई गई, नेविगेशन लाइब्रेरी के नए ऐल्फ़ा वर्शन के बारे में जानने के लिए, Navigation 3 का दस्तावेज़ देखें.

सेटअप

Compose का इस्तेमाल करने के लिए, अपने ऐप्लिकेशन मॉड्यूल की build.gradle फ़ाइल में इस डिपेंडेंसी का इस्तेमाल करें:

Groovy

dependencies {
    def nav_version = "2.9.3"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.3"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

शुरू करें

किसी ऐप्लिकेशन में नेविगेशन लागू करते समय, नेविगेशन होस्ट, ग्राफ़, और कंट्रोलर लागू करें. ज़्यादा जानकारी के लिए, नेविगेशन की खास जानकारी देखें.

Compose में NavController बनाने के तरीके के बारे में जानकारी के लिए, नेविगेशन कंट्रोलर बनाएं लेख का Compose सेक्शन देखें.

NavHost बनाना

Compose में NavHost बनाने के तरीके के बारे में जानने के लिए, अपना नेविगेशन ग्राफ़ डिज़ाइन करना लेख का Compose सेक्शन देखें.

किसी कंपोज़ेबल पर जाने के बारे में जानकारी पाने के लिए, आर्किटेक्चर के दस्तावेज़ में किसी डेस्टिनेशन पर नेविगेट करना लेख पढ़ें.

कंपोज़ेबल डेस्टिनेशन के बीच आर्ग्युमेंट पास करने के बारे में जानकारी पाने के लिए, अपना नेविगेशन ग्राफ़ डिज़ाइन करना लेख का Compose सेक्शन देखें.

नेविगेट करते समय जटिल डेटा पाना

हमारा सुझाव है कि नेविगेट करते समय, जटिल डेटा ऑब्जेक्ट को पास न करें. इसके बजाय, नेविगेशन की कार्रवाइयां करते समय, ज़रूरी जानकारी को आर्ग्युमेंट के तौर पर पास करें. जैसे, यूनीक आइडेंटिफ़ायर या आईडी का कोई अन्य फ़ॉर्म:

// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))

जटिल ऑब्जेक्ट को डेटा लेयर जैसे सिंगल सोर्स ऑफ़ ट्रुथ में डेटा के तौर पर सेव किया जाना चाहिए. नेविगेट करके अपने डेस्टिनेशन पर पहुंचने के बाद, पास किए गए आईडी का इस्तेमाल करके, भरोसेमंद सोर्स से ज़रूरी जानकारी लोड की जा सकती है. डेटा लेयर को ऐक्सेस करने के लिए ज़िम्मेदार ViewModel में मौजूद आर्ग्युमेंट को वापस पाने के लिए, ViewModel के SavedStateHandle का इस्तेमाल करें:

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)
}

इन डीप लिंक की मदद से, किसी कंपोज़ेबल के साथ कोई यूआरएल, कार्रवाई या माइम टाइप जोड़ा जा सकता है. डिफ़ॉल्ट रूप से, इन डीप लिंक को बाहरी ऐप्लिकेशन के साथ शेयर नहीं किया जाता. इन डीप लिंक को बाहरी तौर पर उपलब्ध कराने के लिए, आपको अपने ऐप्लिकेशन की manifest.xml फ़ाइल में सही <intent-filter> एलिमेंट जोड़ने होंगे. ऊपर दिए गए उदाहरण में डीप लिंक चालू करने के लिए, आपको मेनिफ़ेस्ट के <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 को ऊपर के लेवल पर तय करके, नेविगेशन को अन्य कॉम्पोनेंट से कनेक्ट किया जा सकता है. जैसे, सबसे नीचे मौजूद नेविगेशन कॉम्पोनेंट. ऐसा करने से, सबसे नीचे मौजूद बार में मौजूद आइकॉन चुनकर नेविगेट किया जा सकता है.

BottomNavigation और BottomNavigationItem कॉम्पोनेंट इस्तेमाल करने के लिए, अपने Android ऐप्लिकेशन में androidx.compose.material डिपेंडेंसी जोड़ें.

Groovy

dependencies {
    implementation "androidx.compose.material:material:1.9.0"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

dependencies {
    implementation("androidx.compose.material:material:1.9.0")
}

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 कंपोज़ेबल में, currentBackStackEntryAsState() फ़ंक्शन का इस्तेमाल करके मौजूदा NavBackStackEntry पाएं. इस एंट्री से आपको मौजूदा NavDestination का ऐक्सेस मिलता है. इसके बाद, हर BottomNavigationItem के चुने गए स्टेटस का पता लगाया जा सकता है. इसके लिए, आइटम के रूट की तुलना मौजूदा डेस्टिनेशन और उसके पैरंट डेस्टिनेशन के रूट से करें. इससे उन मामलों को हैंडल किया जा सकता है जब NavDestination के क्रम का इस्तेमाल करके नेस्टेड नेविगेशन का इस्तेमाल किया जा रहा हो.

आइटम के रूट का इस्तेमाल, onClick लैम्डा को 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 में सबसे नया डेटा अपने-आप अपडेट हो जाता है.

इंटरऑपरेबिलिटी (दूसरे डिवाइस के साथ काम करना)

अगर आपको Compose के साथ Navigation कॉम्पोनेंट का इस्तेमाल करना है, तो आपके पास दो विकल्प हैं:

  • फ़्रैगमेंट के लिए, नेविगेशन कॉम्पोनेंट की मदद से नेविगेशन ग्राफ़ तय करें.
  • Compose destinations का इस्तेमाल करके, Compose में NavHost के साथ नेविगेशन ग्राफ़ तय करें. ऐसा सिर्फ़ तब किया जा सकता है, जब नेविगेशन ग्राफ़ में मौजूद सभी स्क्रीन कंपोज़ेबल हों.

इसलिए, Compose और Views, दोनों का इस्तेमाल करने वाले ऐप्लिकेशन के लिए, हमारा सुझाव है कि वे फ़्रैगमेंट पर आधारित नेविगेशन कॉम्पोनेंट का इस्तेमाल करें. इसके बाद, फ़्रैगमेंट में व्यू-आधारित स्क्रीन, कंपोज़ स्क्रीन, और ऐसी स्क्रीन शामिल होंगी जो व्यू और कंपोज़, दोनों का इस्तेमाल करती हैं. हर फ़्रैगमेंट का कॉन्टेंट Compose में होने के बाद, अगला चरण उन सभी स्क्रीन को Navigation Compose के साथ जोड़ना और सभी फ़्रैगमेंट को हटाना है.

Compose कोड में डेस्टिनेशन बदलने के लिए, ऐसे इवेंट दिखाए जाते हैं जिन्हें हैरारकी में मौजूद किसी भी कंपोज़ेबल को पास किया जा सकता है और ट्रिगर किया जा सकता है:

@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 लैम्डा की मदद से, इनडायरेक्शन का लेवल तय किया जाता है. इससे आपको अपने नेविगेशन कोड को कंपोज़ेबल से अलग करने में मदद मिलती है. यह सुविधा दो तरह से काम करती है:

  • अपने कंपोज़ेबल में सिर्फ़ पार्स किए गए आर्ग्युमेंट पास करें
  • नेविगेट करने के लिए, कंपोज़ेबल को ट्रिगर करने वाले लैम्ब्डा पास करें. इसके बजाय, NavController को ट्रिगर करने वाले लैम्ब्डा पास करें.

उदाहरण के लिए, ProfileScreen कंपोज़ेबल, userId को इनपुट के तौर पर लेता है. साथ ही, उपयोगकर्ताओं को किसी दोस्त के प्रोफ़ाइल पेज पर जाने की अनुमति देता है. इसका सिग्नेचर यह हो सकता है:

@Composable
fun ProfileScreen(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 
}

इस तरह, ProfileScreen कंपोज़ेबल, नेविगेशन से अलग काम करता है. इससे इसे अलग से टेस्ट किया जा सकता है. composable लैंबडा, Navigation API और आपके कंपोज़ेबल के बीच के अंतर को कम करने के लिए ज़रूरी लॉजिक को शामिल करेगा:

@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){ ... }
}

अब नेविगेशन टेस्टिंग आर्टफ़ैक्ट TestNavHostController का इंस्टेंस पास करके, AppNavHost और NavHost में तय किए गए सभी नेविगेशन लॉजिक की जांच की जा सकती है. यूज़र इंटरफ़ेस (यूआई) टेस्ट, आपके ऐप्लिकेशन के शुरुआती डेस्टिनेशन की पुष्टि करता है. 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 का इस्तेमाल करके, अपने दावे की जांच भी की जा सकती है. इसके लिए, navController के currentBackStackEntry का इस्तेमाल करके, मौजूदा रूट की तुलना अनुमानित रूट से करें:

@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
    composeTestRule.onNodeWithContentDescription("All Profiles")
        .performScrollTo()
        .performClick()

    assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}

Compose की टेस्टिंग से जुड़ी बुनियादी बातों के बारे में ज़्यादा जानने के लिए, अपने Compose लेआउट की टेस्टिंग करना और Jetpack Compose में टेस्टिंग करना कोडलैब देखें. नेविगेशन कोड की बेहतर टेस्टिंग के बारे में ज़्यादा जानने के लिए, नेविगेशन की जांच करना गाइड पढ़ें.

ज़्यादा जानें

Jetpack Navigation के बारे में ज़्यादा जानने के लिए, Navigation कॉम्पोनेंट का इस्तेमाल शुरू करना लेख पढ़ें या Jetpack Compose Navigation का कोडलैब पूरा करें.

अपने ऐप्लिकेशन के नेविगेशन को इस तरह से डिज़ाइन करने का तरीका जानें कि वह अलग-अलग स्क्रीन साइज़, ओरिएंटेशन, और फ़ॉर्म फ़ैक्टर के हिसाब से अडजस्ट हो जाए. इसके लिए, रिस्पॉन्सिव यूज़र इंटरफ़ेस (यूआई) के लिए नेविगेशन लेख पढ़ें.

मॉड्यूलर ऐप्लिकेशन में Compose नेविगेशन को ज़्यादा बेहतर तरीके से लागू करने के बारे में जानने के लिए, GitHub पर Now in Android ऐप्लिकेशन देखें. इसमें नेस्ट किए गए ग्राफ़ और बॉटम नेविगेशन बार इंटिग्रेशन जैसे कॉन्सेप्ट शामिल हैं.

सैंपल