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

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

सेटअप

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

ग्रूवी

dependencies {
    def nav_version = "2.8.0"

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

Kotlin

dependencies {
    val nav_version = "2.8.0"

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

शुरू करें

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

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

NavHost बनाना

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

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

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

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

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

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

// …

}

इस तरीके से, कॉन्फ़िगरेशन में बदलाव होने पर डेटा को सुरक्षित रखा जा सकता है. साथ ही, जिस ऑब्जेक्ट को अपडेट या बदला जा रहा है उसमें किसी तरह की गड़बड़ी होने से भी बचा जा सकता है.

आर्ग्युमेंट के तौर पर जटिल डेटा इस्तेमाल करने से बचने के बारे में ज़्यादा जानकारी के लिए, डेस्टिनेशन के बीच डेटा पास करना लेख पढ़ें. साथ ही, आर्ग्युमेंट के तौर पर इस्तेमाल किए जा सकने वाले डेटा टाइप की सूची भी देखें.

नेविगेशन कंपोज, डीप लिंक के साथ काम करता है. इन्हें 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)
}

इन डीप लिंक की मदद से, किसी खास यूआरएल, ऐक्शन या MIME टाइप को किसी कंपोज़ेबल से जोड़ा जा सकता है. डिफ़ॉल्ट रूप से, ये डीप लिंक बाहरी ऐप्लिकेशन को सार्वजनिक नहीं किए जाते. इन डीप लिंक को बाहरी तौर पर उपलब्ध कराने के लिए, आपको अपने ऐप्लिकेशन की 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)
}

इसके बाद, डीप लिंक डेस्टिनेशन पर अपना ऐप्लिकेशन खोलने के लिए, किसी दूसरे PendingIntent की तरह इस deepLinkPendingIntent का इस्तेमाल करें.

नेस्ट किया गया नेविगेशन

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

निचले नेविगेशन बार के साथ इंटिग्रेशन

अपनी कॉम्पोज़ेबल हैरारकी में NavController को ऊपर के लेवल पर तय करके, नेविगेशन को सबसे नीचे मौजूद नेविगेशन कॉम्पोनेंट जैसे अन्य कॉम्पोनेंट से कनेक्ट किया जा सकता है. ऐसा करने से, सबसे नीचे मौजूद बार में मौजूद आइकॉन को चुनकर, नेविगेट किया जा सकेगा.

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

Groovy

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

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

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

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 lambda को 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(...) }
  }
}

यहां आपको NavHost फ़ंक्शन से navController स्थिति को निकालने और उसे BottomNavigation कॉम्पोनेंट के साथ शेयर करने के लिए, NavController.currentBackStackEntryAsState() तरीके का इस्तेमाल करना होता है. इसका मतलब है कि BottomNavigation की स्थिति अपने-आप अप-टू-डेट रहती है.

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

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

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

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

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

@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
    Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}

अपने फ़्रैगमेंट में, NavController ढूंढकर और डेस्टिनेशन पर नेविगेट करके, Compose और फ़्रैगमेंट पर आधारित नेविगेशन कॉम्पोनेंट के बीच ब्रिज बनाया जाता है:

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 lambda, नेविगेशन एपीआई और आपके कॉम्पोज़ेबल के बीच के अंतर को कम करने के लिए, कम से कम लॉजिक को शामिल करेगा:

@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 Compose नेविगेशन कोडलैब इस्तेमाल करें.

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

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

सैंपल