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

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

सेटअप

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

Groovy

dependencies {
    def nav_version = "2.8.4"

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

Kotlin

dependencies {
    val nav_version = "2.8.4"

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

शुरू करें

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

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

NavHost बनाना

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

किसी 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)
}

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

  • फ़्रैगमेंट के लिए, नेविगेशन कॉम्पोनेंट की मदद से नेविगेशन ग्राफ़ तय करें.
  • 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 नेविगेशन के बारे में ज़्यादा जानने के लिए, नेविगेशन कॉम्पोनेंट का इस्तेमाल शुरू करना लेख पढ़ें या Jetpack Compose नेविगेशन कोडलैब में हिस्सा लें.

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

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

सैंपल