पेज किया गया डेटा लोड करें और दिखाएं

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

डेटा सोर्स तय करना

सबसे पहले, डेटा सोर्स की पहचान करने के लिए, PagingSource को लागू करने का तरीका तय करें. PagingSource एपीआई क्लास में load तरीका शामिल होता है. इसे ओवरराइड करके, यह बताया जाता है कि संबंधित डेटा सोर्स से पेज पर मौजूद डेटा को कैसे वापस लाया जाए.

एसिंक्रोनस लोडिंग के लिए Kotlin कोरूटीन का इस्तेमाल करने के लिए, सीधे PagingSource क्लास का इस्तेमाल करें.

कुंजी और वैल्यू के टाइप चुनना

PagingSource<Key, Value> में दो टाइप पैरामीटर हैं: Key और Value. कुंजी, डेटा लोड करने के लिए इस्तेमाल किए जाने वाले आइडेंटिफ़ायर को तय करती है. साथ ही, वैल्यू, डेटा का टाइप होती है. उदाहरण के लिए, अगर Retrofit को Int पेज नंबर पास करके, नेटवर्क से User ऑब्जेक्ट के पेज लोड किए जाते हैं, तो Key टाइप के तौर पर Int और Value टाइप के तौर पर User को चुनें.

PagingSource तय करना

यहां दिए गए उदाहरण में, PagingSource को लागू किया गया है. यह पेज नंबर के हिसाब से आइटम के पेज लोड करता है. Key का टाइप Int है और Value का टाइप User है.

class ExamplePagingSource(
    val backend: ExampleBackendService,
    val query: String
) : PagingSource<Int, User>() {
  override suspend fun load(
    params: LoadParams<Int>
  ): LoadResult<Int, User> {

    init {
        // the data source is expected to be immutable
        // invalidate PagingSource if data source
        // has updated
        backEnd.addDatabaseOnChangedListener {
            invalidate()
        }
    }

    try {
      // Start refresh at page 1 if undefined.
      val nextPageNumber = params.key ?: 1
      val response = backend.searchUsers(query, nextPageNumber)
      return LoadResult.Page(
        data = response.users,
        prevKey = null, // Only paging forward.
        nextKey = nextPageNumber + 1
      )
    } catch (e: Exception) {
      // Handle errors in this block and return LoadResult.Error for
      // expected errors (such as a network failure).
    }
  }

  override fun getRefreshKey(state: PagingState<Int, User>): Int? {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    return state.anchorPosition?.let { anchorPosition ->
      val anchorPage = state.closestPageToPosition(anchorPosition)
      anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
    }
  }
}

आम तौर पर, PagingSource को लागू करने पर, इसके कंस्ट्रक्टर में दिए गए पैरामीटर, load तरीके को पास किए जाते हैं. इससे क्वेरी के लिए सही डेटा लोड किया जाता है. ऊपर दिए गए उदाहरण में, ये पैरामीटर शामिल हैं:

  • backend: डेटा उपलब्ध कराने वाली बैकएंड सर्विस का इंस्टेंस
  • query: backend से बताई गई सेवा को भेजी जाने वाली सर्च क्वेरी

LoadParams ऑब्जेक्ट में, लोड ऑपरेशन के बारे में जानकारी होती है. इसमें लोड की जाने वाली कुंजी और लोड किए जाने वाले आइटम की संख्या शामिल होती है. load में लोड टाइप के बीच अंतर करने के लिए, देखें कि कौनसा LoadParams सबक्लास पास किया गया है: LoadParams.Refresh, LoadParams.Append या LoadParams.Prepend.

LoadResult ऑब्जेक्ट में, लोड ऑपरेशन का नतीजा शामिल होता है. LoadResult एक सील्ड क्लास है. यह तीन में से किसी एक फ़ॉर्मैट में हो सकती है. यह इस बात पर निर्भर करता है कि load कॉल सफल हुआ या नहीं:

  • अगर लोड हो जाता है, तो LoadResult.Page ऑब्जेक्ट दिखाएं. अगर उस दिशा में और डेटा लोड नहीं किया जा सकता, तो nextKey के लिए null या prevKey पास करें.
  • अगर लोड नहीं होता है, तो LoadResult.Error ऑब्जेक्ट दिखाएं.
  • अगर डेटा में बदलाव होने की वजह से PagingSource अब मान्य नहीं है, तो LoadResult.Invalid ऑब्जेक्ट दिखाएं.

यहां दिए गए डायग्राम में दिखाया गया है कि इस उदाहरण में load फ़ंक्शन, हर लोड के लिए कुंजी कैसे हासिल करता है और अगले लोड के लिए कुंजी कैसे उपलब्ध कराता है.

हर लोड कॉल पर, ExamplePagingSource मौजूदा कुंजी लेता है
    और लोड करने के लिए अगली कुंजी दिखाता है.
पहली इमेज. डायग्राम में दिखाया गया है कि load, पासकी का इस्तेमाल और उसे अपडेट कैसे करता है.

PagingSource को लागू करने के लिए, getRefreshKey तरीके को भी लागू करना होगा. यह तरीका, PagingState ऑब्जेक्ट को पैरामीटर के तौर पर लेता है. शुरुआती लोड के बाद डेटा रीफ़्रेश या अमान्य होने पर, यह load तरीके में पास करने के लिए कुंजी दिखाता है. डेटा के बाद के रीफ़्रेश पर, Paging लाइब्रेरी इस तरीके को अपने-आप कॉल करती है. ऑफ़सेट पर आधारित पेज नंबरिंग के लिए, जहां कुंजियां आइटम की पोज़िशन दिखाती हैं, वहां state.anchorPosition के हिसाब से रीफ़्रेश कुंजी का हिसाब लगाया जा सकता है:

// Only for positional or offset PagingSources where Key is a 0-based item index
// rather than a page number.
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
  // Center the refreshed load around the user's current viewport so the list
  // does not jump back to the top on invalidation.
  return state.anchorPosition?.let { anchorPosition ->
    (anchorPosition - state.config.initialLoadSize / 2).coerceAtLeast(0)
  }
}

गड़बड़ियां ठीक करना

डेटा लोड करने के अनुरोध कई वजहों से फ़ेल हो सकते हैं. खास तौर पर, नेटवर्क पर लोड करते समय. लोड होने के दौरान हुई गड़बड़ियों की शिकायत करने के लिए, load तरीके से LoadResult.Error ऑब्जेक्ट को वापस करें.

उदाहरण के लिए, ExamplePagingSource में लोड होने से जुड़ी गड़बड़ियों को पकड़ा जा सकता है और उनकी शिकायत की जा सकती है. इसके लिए, पिछले उदाहरण में load तरीके में यह जोड़ें:

catch (e: IOException) {
  // IOException for network failures.
  return LoadResult.Error(e)
} catch (e: HttpException) {
  // HttpException for any non-2xx HTTP status codes.
  return LoadResult.Error(e)
}

Retrofit की गड़बड़ियों को ठीक करने के बारे में ज़्यादा जानने के लिए, PagingSource एपीआई के संदर्भ में दिए गए सैंपल देखें.

PagingSource, LoadResult.Error ऑब्जेक्ट इकट्ठा करता है और उन्हें यूज़र इंटरफ़ेस (यूआई) पर डिलीवर करता है, ताकि आप उन पर कार्रवाई कर सकें. यूज़र इंटरफ़ेस (यूआई) में लोडिंग की स्थिति दिखाने के बारे में ज़्यादा जानने के लिए, लोडिंग की स्थितियों को मैनेज करना और दिखाना लेख पढ़ें.

PagingData की स्ट्रीम सेट अप करना

इसके बाद, आपको PagingSource के लागू होने से, पेज पर मौजूद डेटा की स्ट्रीम की ज़रूरत होगी. अपने ViewModel में डेटा स्ट्रीम सेट अप करें. Pager क्लास ऐसे तरीके उपलब्ध कराती है जो PagingSource से PagingData ऑब्जेक्ट की रिएक्टिव स्ट्रीम को एक्सपोज़ करते हैं. पेजिंग लाइब्रेरी, डेटा की स्ट्रीम को Flow के तौर पर दिखाती है.

प्रतिक्रिया वाली लाइव स्ट्रीम सेट अप करने के लिए, Pager इंस्टेंस बनाते समय आपको इंस्टेंस को PagingConfig कॉन्फ़िगरेशन ऑब्जेक्ट और एक फ़ंक्शन देना होगा. यह फ़ंक्शन, Pager को बताता है कि आपके PagingSource लागू करने का इंस्टेंस कैसे मिलेगा. आपके पास वैकल्पिक initialKey तय करने का विकल्प भी होता है. जैसे, 1 से शुरू होने वाले पेज नंबर के लिए 1 या ऑफ़सेट पर आधारित पेजिंग के लिए 0. PagingConfig में, pageSize, initialLoadSize, prefetchDistance, और enablePlaceholders को कॉन्फ़िगर किया जा सकता है. prefetchDistance प्रॉपर्टी से यह तय होता है कि लोड किए गए कॉन्टेंट के किसी भी किनारे से कितने आइटम, अगले फ़ेच को ट्रिगर करते हैं.

class UserViewModel(
    private val backend: ExampleBackendService,
    private val query: String
) : ViewModel() {

    val userPagingFlow: Flow<PagingData<User>> = Pager(
        config = PagingConfig(
            pageSize = 20,
            // Load 3 pages initially so the user can scroll immediately without
            // hitting a loading state.
            initialLoadSize = 60,
            // Fetch the next page far enough ahead of the scroll position to
            // avoid visible pauses.
            prefetchDistance = 20,
            enablePlaceholders = true
        ),
        initialKey = 1,
        pagingSourceFactory = {
            ExamplePagingSource(backend, query)
        }
    )
    .flow
    .cachedIn(viewModelScope)
}

cachedIn ऑपरेटर, डेटा स्ट्रीम को शेयर करने लायक बनाता है. साथ ही, लोड किए गए डेटा को दिए गए CoroutineScope के साथ कैश मेमोरी में सेव करता है. cachedIn के बिना, PagingData को फिर से इकट्ठा नहीं किया जा सकता. इस उदाहरण में, लाइफ़साइकल lifecycle-viewmodel-ktx आर्टफ़ैक्ट से मिले viewModelScope का इस्तेमाल किया गया है.

Pager ऑब्जेक्ट, PagingSource ऑब्जेक्ट से load तरीके को कॉल करता है. इससे उसे LoadParams ऑब्जेक्ट मिलता है और बदले में LoadResult ऑब्जेक्ट मिलता है.

अपने यूज़र इंटरफ़ेस (यूआई) में डेटा इकट्ठा करना और दिखाना

पेज वाली स्ट्रीम को यूज़र इंटरफ़ेस (यूआई) से कनेक्ट करने के लिए, अपने ViewModel से फ़्लो पाएं और उसे अपने सूची कंपोज़ेबल में पास करें.

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val userFlow = viewModel.userPagingFlow
    UserList(flow = userFlow)
}

PagingData फ़्लो को LazyPagingItems में बदलने के लिए, collectAsLazyPagingItems का इस्तेमाल करें. इसके बाद, हर आइटम को लेआउट करने के लिए, LazyColumn में items एपीआई का इस्तेमाल करें.

itemKey का इस्तेमाल करके, हर आइटम के लिए एक यूनीक और स्थायी आइडेंटिफ़ायर ज़रूर दें. नीचे दिए गए उदाहरण में, it.id (User.id प्रॉपर्टी का रेफ़रंस) का इस्तेमाल किया गया है. ऐसा इसलिए, क्योंकि डेटा अपडेट होने के दौरान, यह User इंस्टेंस के लिए एक जैसा रहता है.

@Composable
fun UserList(flow: Flow<PagingData<User>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val user = lazyPagingItems[index]
            if (user != null) {
                UserRow(user)
            } else {
                UserPlaceholder()
            }
        }
    }
}

पेज लोड होने के दौरान, Paging लाइब्रेरी प्लेसहोल्डर के लिए null का इस्तेमाल करती है. इसलिए, अगर आपने प्लेसहोल्डर चालू किए हैं, तो आपको कॉन्टेंट ब्लॉक में null वैल्यू को मैनेज करना होगा.

अब सूची में पेज के हिसाब से डेटा दिखता है. साथ ही, उपयोगकर्ता के स्क्रोल करने पर, पेजिंग लाइब्रेरी अतिरिक्त पेज लोड करती है.

अन्य संसाधन

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

दस्तावेज़

कॉन्टेंट देखना