डेटा स्ट्रीम बदलना

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

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

इस पेज पर यह माना गया है कि आपको पेजिंग लाइब्रेरी के बुनियादी इस्तेमाल के बारे में पता है.

बेसिक ट्रांसफ़ॉर्मेशन लागू करना

PagingData को रिएक्टिव स्ट्रीम में शामिल किया जाता है. इसलिए, डेटा लोड करने और उसे दिखाने के बीच, डेटा में धीरे-धीरे बदलाव किया जा सकता है.

स्ट्रीम में मौजूद हर PagingData ऑब्जेक्ट पर ट्रांसफ़ॉर्मेशन लागू करने के लिए, स्ट्रीम पर map() ऑपरेशन के अंदर ट्रांसफ़ॉर्मेशन रखें:

pager.flow // Type is Flow<PagingData<User>>.
  // Map the outer stream so that the transformations are applied to
  // each new generation of PagingData.
  .map { pagingData ->
    // Transformations in this block are applied to the items
    // in the paged data.
}

डेटा को बदलना

डेटा की स्ट्रीम पर सबसे बुनियादी कार्रवाई, उसे किसी दूसरे टाइप में बदलना है. PagingData ऑब्जेक्ट का ऐक्सेस मिलने के बाद, PagingData ऑब्जेक्ट में मौजूद पेज वाली सूची में मौजूद हर आइटम पर map() ऑपरेशन किया जा सकता है.

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

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.map { user -> UiModel(user) }
  }

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

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

private val queryFlow = MutableStateFlow("")

fun onQueryChanged(query: String) {
  queryFlow.value = query
}

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

val querySearchResults: Flow<User> = queryFlow.flatMapLatest { query ->
  // The database query returns a Flow which is output through
  // querySearchResults
  userDatabase.searchBy(query)
}

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

डेटा फ़िल्टर करना

फ़िल्टर करना भी एक सामान्य ऑपरेशन है. उपयोगकर्ता की ओर से तय की गई शर्तों के आधार पर, डेटा को फ़िल्टर किया जा सकता है. इसके अलावा, अगर डेटा को किसी अन्य शर्त के आधार पर छिपाना है, तो उसे यूज़र इंटरफ़ेस (यूआई) से हटाया जा सकता है.

आपको फ़िल्टर करने की इन कार्रवाइयों को map() कॉल के अंदर रखना होगा, क्योंकि फ़िल्टर PagingData ऑब्जेक्ट पर लागू होता है. डेटा को PagingData से फ़िल्टर करने के बाद, नए PagingData इंस्टेंस को यूज़र इंटरफ़ेस (यूआई) लेयर को दिखाया जाता है.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
  }

सूची सेपरेटर जोड़ना

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

पेज वाली सूची में सेपरेटर डालने के लिए, ये तीन चरण पूरे करें:

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

यूज़र इंटरफ़ेस (यूआई) मॉडल को बदलना

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

मान लें कि आपको User आइटम की पेज वाली सूची में सेपरेटर जोड़ने हैं. यहां दिए गए स्निपेट में, एक बेस क्लास बनाने का तरीका बताया गया है. इसमें इंस्टेंस, UserModel या SeparatorModel हो सकते हैं:

sealed class UiModel {
  class UserModel(val id: String, val label: String) : UiModel() {
    constructor(user: User) : this(user.id, user.label)
  }

  class SeparatorModel(val description: String) : UiModel()
}

डेटा स्ट्रीम में बदलाव करना

डेटा स्ट्रीम को लोड करने के बाद और उसे दिखाने से पहले, आपको उस पर ट्रांसफ़ॉर्मेशन लागू करने होंगे. ट्रांसफ़ॉर्मेशन से ये काम होने चाहिए:

  • लोड किए गए सूची आइटम को नए बेस आइटम टाइप के हिसाब से बदलें.
  • सेपरेटर जोड़ने के लिए, PagingData.insertSeparators() तरीके का इस्तेमाल करें.

ट्रांसफ़ॉर्मेशन के बारे में ज़्यादा जानने के लिए, बुनियादी ट्रांसफ़ॉर्मेशन लागू करना लेख पढ़ें.

यहां दिए गए उदाहरण में, PagingData<User> स्ट्रीम को अपडेट करके PagingData<UiModel> स्ट्रीम में बदलने के लिए ट्रांसफ़ॉर्मेशन ऑपरेशन दिखाए गए हैं. इसमें सेपरेटर जोड़े गए हैं:

pager.flow.map { pagingData: PagingData<User> ->
  // Map outer stream, so you can perform transformations on
  // each paging generation.
  pagingData
  .map { user ->
    // Convert items in stream to UiModel.UserModel.
    UiModel.UserModel(user)
  }
  .insertSeparators<UiModel.UserModel, UiModel> { before, after ->
    when {
      before == null -> UiModel.SeparatorModel("HEADER")
      after == null -> UiModel.SeparatorModel("FOOTER")
      shouldSeparate(before, after) -> UiModel.SeparatorModel(
        "BETWEEN ITEMS $before AND $after"
      )
      // Return null to avoid adding a separator between two items.
      else -> null
    }
  }
}

यूज़र इंटरफ़ेस में सेपरेटर मैनेज करना

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

@Composable fun UserList(pagingItems: LazyPagingItems) {
  LazyColumn {
    items(
      count = pagingItems.itemCount,
      key = { index ->
        val item = pagingItems.peek(index)
        when (item) {
          is UiModel.UserModel -> item.user.id
          is UiModel.SeparatorModel -> item.description
          else -> index
        }
      }
    ) { index ->
      when (val item = pagingItems[index]) {
        is UiModel.UserModel -> UserItemComposable(item.user)
        is UiModel.SeparatorModel -> SeparatorComposable(item.description)
        null -> PlaceholderComposable()
      }
    }
  }
}

डुप्लीकेट काम से बचें

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

cachedIn() ऑपरेशन, इससे पहले होने वाले सभी ट्रांसफ़ॉर्मेशन के नतीजों को कैश मेमोरी में सेव करता है. आम तौर पर, इस ऑपरेटर को ViewModel में लागू किया जाता है. ऐसा Flow को कंपोज़ेबल के तौर पर दिखाने से पहले किया जाता है.

कैश को सही तरीके से मैनेज करने के लिए, cachedIn() को CoroutineScope पास करें. ऐसा करने का तरीका, viewModelScope का इस्तेमाल करके यहां दिए गए उदाहरण में दिखाया गया है.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
      .map { user -> UiModel.UserModel(user) }
  }
  .cachedIn(viewModelScope)

PagingData की स्ट्रीम के साथ cachedIn() का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, PagingData की स्ट्रीम सेट अप करना लेख पढ़ें.

अन्य संसाधन

पेजिंग लाइब्रेरी के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें:

दस्तावेज़

कॉन्टेंट देखता है