रिस्पॉन्सिव यूज़र इंटरफ़ेस (यूआई) के लिए नेविगेशन

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

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

Jetpack नेविगेशन कॉम्पोनेंट नेविगेशन के सिद्धांतों को लागू करता है. साथ ही, इसका इस्तेमाल रिस्पॉन्सिव यूआई वाले ऐप्लिकेशन बनाने में मदद करने के लिए किया जा सकता है.

पहली इमेज. नेविगेशन पैनल, रेल, और बॉटम बार के साथ बड़े, मीडियम, और छोटे डिसप्ले.

रिस्पॉन्सिव यूज़र इंटरफ़ेस (यूआई) नेविगेशन

ऐप्लिकेशन की डिसप्ले विंडो का साइज़, एर्गोनॉमिक्स और उपयोगिता पर निर्भर करता है. विंडो साइज़ क्लास की मदद से, सही नेविगेशन एलिमेंट (जैसे कि नेविगेशन बार, रेल या ड्रॉर) तय किए जा सकते हैं और उन्हें ऐसी जगह पर रखा जा सकता है जहां उपयोगकर्ता उन्हें आसानी से ऐक्सेस कर सके. मटीरियल डिज़ाइन के लेआउट से जुड़े दिशा-निर्देश में, नेविगेशन एलिमेंट, डिसप्ले के सबसे ऊपरी किनारे पर स्थायी जगह लेते हैं. ऐप्लिकेशन की चौड़ाई कम होने पर, ये एलिमेंट सबसे नीचे वाले किनारे तक जा सकते हैं. नेविगेशन एलिमेंट की आपकी पसंद, काफ़ी हद तक ऐप्लिकेशन विंडो के साइज़ और एलिमेंट में मौजूद आइटम की संख्या पर निर्भर करती है.

विंडो के साइज़ की क्लास कुछ आइटम कई आइटम
छोटी चौड़ाई सबसे नीचे मौजूद नेविगेशन बार नेविगेशन पैनल (सबसे बड़ा किनारा या सबसे नीचे)
सामान्य चौड़ाई नेविगेशन रेल नेविगेशन पैनल (सबसे पहला किनारा)
बढ़ाई गई चौड़ाई नेविगेशन रेल स्थायी नेविगेशन पैनल (सबसे पहला किनारे)

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

व्यू

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigationrail.NavigationRailView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w1240dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

Compose

// This method should be run inside a Composable function.
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
// You can get the height of the current window by invoking heightSizeClass instead.

@Composable
fun MyApp(widthSizeClass: WindowWidthSizeClass) {
    // Select a navigation element based on window size.
    when (widthSizeClass) {
        WindowWidthSizeClass.Compact -> { CompactScreen() }
        WindowWidthSizeClass.Medium -> { MediumScreen() }
        WindowWidthSizeClass.Expanded -> { ExpandedScreen() }
    }
}

@Composable
fun CompactScreen() {
    Scaffold(bottomBar = {
                NavigationBar {
                    icons.forEach { item ->
                        NavigationBarItem(
                            selected = isSelected,
                            onClick = { ... },
                            icon = { ... })
                    }
                }
            }
        ) {
        // Other content
    }
}

@Composable
fun MediumScreen() {
    Row(modifier = Modifier.fillMaxSize()) {
        NavigationRail {
            icons.forEach { item ->
                NavigationRailItem(
                    selected = isSelected,
                    onClick = { ... },
                    icon = { ... })
            }
        }
        // Other content
    }
}

@Composable
fun ExpandedScreen() {
    PermanentNavigationDrawer(
        drawerContent = {
            icons.forEach { item ->
                NavigationDrawerItem(
                    icon = { ... },
                    label = { ... },
                    selected = isSelected,
                    onClick = { ... }
                )
            }
        },
        content = {
            // Other content
        }
    )
}

रिस्पॉन्सिव कॉन्टेंट डेस्टिनेशन

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

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

विंडो का साइज़ बदलने से होने पर, नेविगेशन नहीं करना चाहिए. सिर्फ़ अलग-अलग साइज़ की विंडो को अडजस्ट करने के लिए, कॉन्टेंट के डेस्टिनेशन न बनाएं. उदाहरण के लिए, फ़ोल्ड किए जा सकने वाले डिवाइस की अलग-अलग स्क्रीन के लिए, कॉन्टेंट के अलग-अलग डेस्टिनेशन न बनाएं.

विंडो का साइज़ बदलने पर होने वाले खराब असर के तौर पर जाने में ये समस्याएं होती हैं:

  • नई मंज़िल पर जाने से पहले, पुराना डेस्टिनेशन (पिछली विंडो के साइज़ के लिए) कुछ समय के लिए दिख सकता है
  • हर विंडो साइज़ के लिए, नेविगेशन की ज़रूरत होती है, ताकि डिवाइस के रिवर्सलिटी बनी रहे. उदाहरण के लिए, जब डिवाइस को फ़ोल्ड या अनफ़ोल्ड किया जा रहा हो
  • एक डेस्टिनेशन के बीच ऐप्लिकेशन की स्थिति को बनाए रखना मुश्किल हो सकता है, क्योंकि नेविगेट करने से बैकस्टैक के पॉप-अप होने पर स्टेटस मिट सकता है

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

अगर आपके ऐप्लिकेशन को विंडो के साइज़ के हिसाब से कॉन्टेंट के लिए यूनीक डेस्टिनेशन की ज़रूरत है, तो काम के डेस्टिनेशन को एक ही डेस्टिनेशन में जोड़ें. इसमें अन्य लेआउट भी शामिल हैं.

वैकल्पिक लेआउट वाले कॉन्टेंट के डेस्टिनेशन

रिस्पॉन्सिव डिज़ाइन के हिस्से के तौर पर, एक नेविगेशन डेस्टिनेशन में ऐप्लिकेशन की विंडो के साइज़ के आधार पर वैकल्पिक लेआउट हो सकते हैं. हर लेआउट पूरी विंडो पर एक जगह ले लेता है, लेकिन यहां अलग-अलग साइज़ की विंडो के लिए अलग-अलग लेआउट दिखाए जाते हैं.

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

व्यू

SlidingPaneLayout की मदद से, एक ऐसा नेविगेशन डेस्टिनेशन बनाया जा सकता है जो बड़ी स्क्रीन पर कॉन्टेंट के दो पैनल साथ-साथ दिखाता है. हालांकि, छोटी स्क्रीन वाले डिवाइस, जैसे कि फ़ोन पर एक बार में सिर्फ़ एक पैनल दिखता है.

<!-- Single destination for list and detail. -->

<navigation ...>

    <!-- Fragment that implements SlidingPaneLayout. -->
    <fragment
        android:id="@+id/article_two_pane"
        android:name="com.example.app.ListDetailTwoPaneFragment" />

    <!-- Other destinations... -->
</navigation>

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

Compose

Compose में, वैकल्पिक कंपोज़ेबल को किसी एक रूट में जोड़कर, लिस्ट की जानकारी वाला व्यू लागू किया जा सकता है. इसमें, हर साइज़ क्लास के लिए सही कंपोज़ेबल निकलने के लिए, विंडो साइज़ क्लास का इस्तेमाल किया जाता है.

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

सूची की ज़्यादा जानकारी वाले व्यू में तीन कंपोज़ेबल होते हैं. उदाहरण के लिए:

/* Displays a list of items. */
@Composable
fun ListOfItems(
    onItemSelected: (String) -> Unit,
) { /*...*/ }

/* Displays the detail for an item. */
@Composable
fun ItemDetail(
    selectedItemId: String? = null,
) { /*...*/ }

/* Displays a list and the detail for an item side by side. */
@Composable
fun ListAndDetail(
    selectedItemId: String? = null,
    onItemSelected: (String) -> Unit,
) {
  Row {
    ListOfItems(onItemSelected = onItemSelected)
    ItemDetail(selectedItemId = selectedItemId)
  }
}

एक ही नेविगेशन रूट से, सूची की ज़्यादा जानकारी वाले व्यू को ऐक्सेस किया जा सकता है:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      /*...*/
    )
  } else {
    // If the display size cannot accommodate both the list and the item detail,
    // show one of them based on the user's focus.
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(/*...*/)
    }
  }
}

ListDetailRoute (नेविगेशन डेस्टिनेशन) तय करता है कि तीन कंपोज़ेबल में से किस कंपोज़ेबल का उत्सर्जन होना चाहिए: विंडो के साइज़ को बड़ा करने के लिए ListAndDetail; छोटे के लिए ListOfItems या ItemDetail. यह इस बात पर निर्भर करता है कि सूची में मौजूद किसी आइटम को चुना गया है या नहीं.

रास्ता NavHost में शामिल है, उदाहरण के लिए:

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  /*...*/
}

अपने ऐप्लिकेशन के WindowMetrics की जांच करके isExpandedWindowSize तर्क दिया जा सकता है.

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

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

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

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }

  fun onItemBackPress() {
    viewModelState.update {
      it.copy(selectedItemId = null)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
    onItemBackPress: () -> Unit = { listDetailViewModel.onItemBackPress() },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
      BackHandler {
        onItemBackPress()
      }
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

ViewModel की विंडो के साइज़ की क्लास की जानकारी के साथ ऐप्लिकेशन की स्थिति को जोड़ने पर, सही कंपोज़ेबल चुन लिया जाता है. यह काफ़ी आसान है. एकतरफ़ा डेटा फ़्लो को बनाए रखने से, आपका ऐप्लिकेशन उपलब्ध डिसप्ले स्पेस का पूरी तरह से इस्तेमाल कर पाएगा. साथ ही, ऐप्लिकेशन की स्थिति बरकरार रहेगी.

Compose में सूची की जानकारी वाले व्यू को लागू करने की पूरी प्रोसेस के लिए, GitHub पर JetNews का सैंपल देखें.

एक नेविगेशन ग्राफ़

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

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

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

आपके ऐप्लिकेशन में कॉन्टेंट का एक ऐसा डेस्टिनेशन शामिल हो सकता है जिसके कॉन्टेंट का डेस्टिनेशन मौजूद हो. उदाहरण के लिए, सूची की ज़्यादा जानकारी वाले व्यू में, आइटम की जानकारी वाले पैनल में ऐसे यूज़र इंटरफ़ेस (यूआई) एलिमेंट शामिल हो सकते हैं जो आइटम की जानकारी बदलने वाले कॉन्टेंट पर नेविगेट करते हैं.

इस तरह के सब-नेविगेशन को लागू करने के लिए, जानकारी वाले पैनल में, नेस्ट किया गया नेविगेशन होस्ट हो सकता है. इस होस्ट का अपना नेविगेशन ग्राफ़ होता है. इसमें, ब्यौरे वाले पैनल से ऐक्सेस किए जाने वाले डेस्टिनेशन तय होते हैं:

व्यू

<!-- layout/two_pane_fragment.xml -->

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_pane"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"/>

    <!-- Detail pane is a nested navigation host. Its graph is not connected
         to the main graph that contains the two_pane_fragment destination. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detail_pane"
        android:layout_width="300dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

Compose

@Composable
fun ItemDetail(selectedItemId: String? = null) {
    val navController = rememberNavController()
    NavHost(navController, "itemSubdetail1") {
        composable("itemSubdetail1") { ItemSubdetail1(...) }
        composable("itemSubdetail2") { ItemSubdetail2(...) }
        composable("itemSubdetail3") { ItemSubdetail3(...) }
    }
}

यह नेस्ट किए गए नेविगेशन ग्राफ़ से अलग है, क्योंकि नेस्ट किए गए NavHost का नेविगेशन ग्राफ़, मुख्य नेविगेशन ग्राफ़ से कनेक्ट नहीं किया गया है; इसका मतलब है कि एक ग्राफ़ में मौजूद डेस्टिनेशन से दूसरे ग्राफ़ में सीधे नेविगेट नहीं किया जा सकता.

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

सुरक्षित स्थिति

रिस्पॉन्सिव कॉन्टेंट डेस्टिनेशन उपलब्ध कराने के लिए, आपके ऐप्लिकेशन को डिवाइस को घुमाने या फ़ोल्ड करने या ऐप्लिकेशन की विंडो का साइज़ बदलने पर उसकी स्थिति को बनाए रखना होगा. डिफ़ॉल्ट रूप से, कॉन्फ़िगरेशन में इस तरह के बदलाव होते हैं कि ऐप्लिकेशन की गतिविधियां, फ़्रैगमेंट, व्यू हैरारकी, और कंपोज़ेबल. हमारा सुझाव है कि यूज़र इंटरफ़ेस (यूआई) की स्थिति को सेव करने के लिए, ViewModel या rememberSaveable का इस्तेमाल करें, जो कॉन्फ़िगरेशन में होने वाले बदलावों के दौरान भी सेव रहती है. (यूज़र इंटरफ़ेस (यूआई) की स्थितियां सेव करना और स्टेटस और Jetpack Compose देखें.)

साइज़ में किए गए बदलाव ऐसे होने चाहिए जिन्हें पहले जैसा किया जा सके. उदाहरण के लिए, जब कोई उपयोगकर्ता डिवाइस को घुमाता है और फिर उसे वापस घुमाता है.

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

ViewModel के स्कोप

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

ViewModel को हमेशा लाइफ़साइकल के दायरे में रखा जाता है. जब लाइफ़साइकल हमेशा के लिए खत्म हो जाता है, तो ViewModel को हटा दिया जाता है और इसे खारिज किया जा सकता है. यह तय करता है कि ViewModel पाने के लिए किस प्रॉपर्टी डेलिगेट का इस्तेमाल किया गया है. यह भी तय होता है कि ViewModel को कितने बड़े पैमाने पर शेयर किया जा सकता है. यह लाइफ़साइकल, ViewModel के स्कोप वाले दायरे में आता है.

सबसे आसान मामले में, हर नेविगेशन डेस्टिनेशन वाला एक फ़्रैगमेंट होता है, जिसमें पूरी तरह से अलग यूज़र इंटरफ़ेस (यूआई) स्थिति होती है; इसलिए, हर फ़्रैगमेंट उस फ़्रैगमेंट के स्कोप का ViewModel पाने के लिए, viewModels() प्रॉपर्टी डेलिगेट का इस्तेमाल कर सकता है.

फ़्रैगमेंट के बीच यूज़र इंटरफ़ेस (यूआई) स्थिति शेयर करने के लिए, फ़्रैगमेंट में activityViewModels() को कॉल करके गतिविधि में ViewModel का स्कोप करें (गतिविधि के लिए बराबर की वैल्यू सिर्फ़ viewModels() है). इससे, गतिविधि और इसमें अटैच किए गए सभी फ़्रैगमेंट को ViewModel इंस्टेंस को शेयर करने की अनुमति मिलती है. हालांकि, सिंगल-ऐक्टिविटी आर्किटेक्चर में ViewModel स्कोप तब तक बेहतर तरीके से काम करता है, जब तक ऐप्लिकेशन काम करता है. इससे ViewModel, मेमोरी में बना रहता है, भले ही कोई फ़्रैगमेंट उसका इस्तेमाल न कर रहा हो.

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

इसके बजाय, मौजूदा NavController में नेविगेशन ग्राफ़ के लिए ViewModel का दायरा तय करें. चेकआउट फ़्लो में शामिल डेस्टिनेशन को शामिल करने के लिए, नेस्ट किया गया नेविगेशन ग्राफ़ बनाएं. इसके बाद, उन सभी फ़्रैगमेंट डेस्टिनेशन में, navGraphViewModels() प्रॉपर्टी डेलिगेट का इस्तेमाल करें और शेयर किए गए ViewModel को पाने के लिए, नेविगेशन ग्राफ़ का आईडी पास करें. इससे यह पक्का होता है कि उपयोगकर्ता के चेकआउट फ़्लो से बाहर निकलने और नेस्ट किया गया नेविगेशन ग्राफ़, स्कोप के बाहर हो जाने पर, ViewModel से जुड़े इंस्टेंस को खारिज कर दिया जाएगा. साथ ही, अगले चेकआउट के लिए इसका इस्तेमाल नहीं किया जाएगा.

दायरा प्रॉपर्टी का ऐक्सेस ViewModel इनके साथ शेयर किया जा सकता है
फ़्रैगमेंट Fragment.viewModels() सिर्फ़ मौजूदा फ़्रैगमेंट
गतिविधि Activity.viewModels()

Fragment.activityViewModels()

गतिविधि और उससे जुड़े सभी फ़्रैगमेंट
नेविगेशन ग्राफ़ Fragment.navGraphViewModels() एक ही नेविगेशन ग्राफ़ में सभी फ़्रैगमेंट

ध्यान दें कि अगर आपने नेस्ट किए गए नेविगेशन होस्ट (ऊपर देखें) का इस्तेमाल किया है, तो navGraphViewModels() का इस्तेमाल करने पर उस होस्ट में डेस्टिनेशन, होस्ट से बाहर के डेस्टिनेशन के साथ ViewModel शेयर नहीं कर सकते. ऐसा इसलिए, क्योंकि ग्राफ़ कनेक्ट नहीं हैं. इस मामले में, इसके बजाय गतिविधि के स्कोप का इस्तेमाल किया जा सकता है.

होस्टेड स्टेट

कंपोज़ में, स्टेट लिफ़्टिंग की मदद से विंडो का साइज़ बदलने के दौरान स्थिति को बनाए रखा जा सकता है. कंपोज़ेबल की स्थिति को कंपोज़ेबल ट्री में ऊपर की पोज़िशन पर रखने से, उस स्थिति को बनाए रखा जा सकता है, भले ही कंपोज़ेबल न दिख रहे हों.

ऊपर दिए गए वैकल्पिक लेआउट के साथ कॉन्टेंट के डेस्टिनेशन सेक्शन के लिखें सेक्शन में, हमने सूची की जानकारी वाले व्यू कंपोज़ेबल के स्टेटस को ListDetailRoute पर सेट कर दिया है, ताकि इस स्थिति को बनाए रखा जा सके, चाहे कोई भी कंपोज़ेबल दिखाया गया हो:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) { /*...*/ }

अन्य संसाधन