पेजिंग लाइब्रेरी, बड़े डेटासेट से पेज किए गए डेटा को लोड करने और दिखाने के लिए बेहतरीन सुविधाएं उपलब्ध कराती है. इस गाइड में, नेटवर्क डेटा सोर्स से पेज किए गए डेटा की स्ट्रीम सेट अप करने और उसे RecyclerView
में दिखाने के लिए, पेजिंग लाइब्रेरी का इस्तेमाल करने का तरीका बताया गया है.
डेटा सोर्स तय करना
सबसे पहले, डेटा सोर्स की पहचान करने के लिए, PagingSource
लागू करने का तरीका तय करें. PagingSource
एपीआई क्लास में load()
वाला तरीका शामिल होता है. इसे बदलकर, उस डेटा सोर्स से पेज किया गया डेटा पाने का तरीका बताया जाता है.
एसिंक्रोनस लोडिंग के लिए Kotlin कोरूटीन का इस्तेमाल करने के लिए, सीधे PagingSource
क्लास का इस्तेमाल करें. पेजिंग लाइब्रेरी, अन्य असाइनक्टिव फ़्रेमवर्क के साथ काम करने के लिए क्लास भी उपलब्ध कराती है:
- RxJava का इस्तेमाल करने के लिए, इसके बजाय
RxPagingSource
को लागू करें. - Guava के
ListenableFuture
का इस्तेमाल करने के लिए, इसके बजायListenableFuturePagingSource
का इस्तेमाल करें.
कुंजी और वैल्यू के टाइप चुनना
PagingSource<Key, Value>
में दो टाइप पैरामीटर होते हैं: Key
और Value
. कुंजी से, डेटा लोड करने के लिए इस्तेमाल किए गए आइडेंटिफ़ायर के बारे में पता चलता है. साथ ही, वैल्यू से डेटा का टाइप पता चलता है. उदाहरण के लिए, अगर Retrofit को Int
पेज नंबर पास करके, नेटवर्क से User
ऑब्जेक्ट के पेज लोड किए जाते हैं, तो Int
को Key
टाइप और User
को Value
टाइप के तौर पर चुनें.
PagingSource तय करना
यहां दिए गए उदाहरण में, एक ऐसा PagingSource
लागू किया गया है जो पेज नंबर के हिसाब से आइटम के पेज लोड करता है. Key
टाइप Int
है और Value
टाइप User
है.
Kotlin
class ExamplePagingSource( val backend: ExampleBackendService, val query: String ) : PagingSource<Int, User>() { override suspend fun load( params: LoadParams<Int> ): LoadResult<Int, User> { 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 = response.nextPageNumber ) } 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) } } }
Java
class ExamplePagingSource extends RxPagingSource<Integer, User> { @NonNull private ExampleBackendService mBackend; @NonNull private String mQuery; ExamplePagingSource(@NonNull ExampleBackendService backend, @NonNull String query) { mBackend = backend; mQuery = query; } @NotNull @Override public Single<LoadResult<Integer, User>> loadSingle( @NotNull LoadParams<Integer> params) { // Start refresh at page 1 if undefined. Integer nextPageNumber = params.getKey(); if (nextPageNumber == null) { nextPageNumber = 1; } return mBackend.searchUsers(mQuery, nextPageNumber) .subscribeOn(Schedulers.io()) .map(this::toLoadResult) .onErrorReturn(LoadResult.Error::new); } private LoadResult<Integer, User> toLoadResult( @NonNull SearchUserResponse response) { return new LoadResult.Page<>( response.getUsers(), null, // Only paging forward. response.getNextPageNumber(), LoadResult.Page.COUNT_UNDEFINED, LoadResult.Page.COUNT_UNDEFINED); } @Nullable @Override public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) { // 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. Integer anchorPosition = state.getAnchorPosition(); if (anchorPosition == null) { return null; } LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition); if (anchorPage == null) { return null; } Integer prevKey = anchorPage.getPrevKey(); if (prevKey != null) { return prevKey + 1; } Integer nextKey = anchorPage.getNextKey(); if (nextKey != null) { return nextKey - 1; } return null; } }
Java
class ExamplePagingSource extends ListenableFuturePagingSource<Integer, User> { @NonNull private ExampleBackendService mBackend; @NonNull private String mQuery; @NonNull private Executor mBgExecutor; ExamplePagingSource( @NonNull ExampleBackendService backend, @NonNull String query, @NonNull Executor bgExecutor) { mBackend = backend; mQuery = query; mBgExecutor = bgExecutor; } @NotNull @Override public ListenableFuture<LoadResult<Integer, User>> loadFuture(@NotNull LoadParams<Integer> params) { // Start refresh at page 1 if undefined. Integer nextPageNumber = params.getKey(); if (nextPageNumber == null) { nextPageNumber = 1; } ListenableFuture<LoadResult<Integer, User>> pageFuture = Futures.transform(mBackend.searchUsers(mQuery, nextPageNumber), this::toLoadResult, mBgExecutor); ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture = Futures.catching(pageFuture, HttpException.class, LoadResult.Error::new, mBgExecutor); return Futures.catching(partialLoadResultFuture, IOException.class, LoadResult.Error::new, mBgExecutor); } private LoadResult<Integer, User> toLoadResult(@NonNull SearchUserResponse response) { return new LoadResult.Page<>(response.getUsers(), null, // Only paging forward. response.getNextPageNumber(), LoadResult.Page.COUNT_UNDEFINED, LoadResult.Page.COUNT_UNDEFINED); } @Nullable @Override public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) { // 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. Integer anchorPosition = state.getAnchorPosition(); if (anchorPosition == null) { return null; } LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition); if (anchorPage == null) { return null; } Integer prevKey = anchorPage.getPrevKey(); if (prevKey != null) { return prevKey + 1; } Integer nextKey = anchorPage.getNextKey(); if (nextKey != null) { return nextKey - 1; } return null; } }
किसी क्वेरी के लिए सही डेटा लोड करने के लिए, PagingSource
को लागू करने का सामान्य तरीका, अपने कन्स्ट्रक्टर में दिए गए पैरामीटर को load()
तरीके में पास करता है. ऊपर दिए गए उदाहरण में, ये पैरामीटर हैं:
backend
: डेटा देने वाली बैकएंड सेवा का एक इंस्टेंसquery
:backend
से दिखाई गई सेवा को भेजी जाने वाली खोज क्वेरी
LoadParams
ऑब्जेक्ट में, लोड करने के लिए किए जाने वाले ऑपरेशन के बारे में जानकारी होती है. इसमें, लोड किए जाने वाले पासकोड और लोड किए जाने वाले आइटम की संख्या शामिल होती है.
LoadResult
ऑब्जेक्ट में, लोड करने की कार्रवाई का नतीजा होता है. LoadResult
एक सील की गई क्लास है, जो इनमें से किसी एक फ़ॉर्म में दिखती है. यह इस बात पर निर्भर करता है कि load()
कॉल पूरा हुआ या नहीं:
- अगर लोड हो जाता है, तो
LoadResult.Page
ऑब्जेक्ट दिखाएं. - अगर विज्ञापन लोड नहीं होता है, तो
LoadResult.Error
ऑब्जेक्ट दिखाएं.
नीचे दिए गए इलस्ट्रेशन में दिखाया गया है कि इस उदाहरण में load()
फ़ंक्शन, हर लोड के लिए कुंजी कैसे पाता है और अगले लोड के लिए कुंजी कैसे उपलब्ध कराता है.
PagingSource
को लागू करने के लिए, ऐसा getRefreshKey()
तरीका भी लागू करना होगा जो PagingState
ऑब्जेक्ट को पैरामीटर के तौर पर लेता हो. यह load()
तरीके में पास करने के लिए वह कुंजी दिखाता है जिसे शुरुआती लोड के बाद, डेटा रीफ़्रेश या अमान्य होने पर पास किया जाता है. डेटा को फिर से रीफ़्रेश करने पर, पेजिंग लाइब्रेरी इस तरीके को अपने-आप कॉल करती है.
गड़बड़ियां ठीक करना
डेटा लोड करने के अनुरोध कई वजहों से पूरे नहीं हो पाते. खास तौर पर, नेटवर्क से लोड करने पर ऐसा हो सकता है. load()
तरीके से LoadResult.Error
ऑब्जेक्ट को दिखाकर, लोड करने के दौरान हुई गड़बड़ियों की शिकायत करें.
उदाहरण के लिए, पिछले उदाहरण में दिए गए ExamplePagingSource
में लोड करने से जुड़ी गड़बड़ियों को पकड़कर उनकी शिकायत की जा सकती है. इसके लिए, load()
तरीके में ये चीज़ें जोड़ें:
Kotlin
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) }
Java
return backend.searchUsers(searchTerm, nextPageNumber) .subscribeOn(Schedulers.io()) .map(this::toLoadResult) .onErrorReturn(LoadResult.Error::new);
Java
ListenableFuture<LoadResult<Integer, User>> pageFuture = Futures.transform( backend.searchUsers(query, nextPageNumber), this::toLoadResult, bgExecutor); ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture = Futures.catching( pageFuture, HttpException.class, LoadResult.Error::new, bgExecutor); return Futures.catching(partialLoadResultFuture, IOException.class, LoadResult.Error::new, bgExecutor);
Retrofit गड़बड़ियों को मैनेज करने के बारे में ज़्यादा जानने के लिए, PagingSource
एपीआई रेफ़रंस में सैंपल देखें.
PagingSource
, यूज़र इंटरफ़ेस (यूआई) में LoadResult.Error
ऑब्जेक्ट इकट्ठा और डिलीवर करता है, ताकि आप उन पर कार्रवाई कर सकें. यूज़र इंटरफ़ेस (यूआई) में लोडिंग स्टेटस दिखाने के बारे में ज़्यादा जानने के लिए, लोडिंग स्टेटस मैनेज करना और दिखाना लेख पढ़ें.
PagingData की स्ट्रीम सेट अप करना
इसके बाद, आपको PagingSource
लागू करने से, पेज किए गए डेटा की स्ट्रीम की ज़रूरत होगी.
अपने ViewModel
में डेटा स्ट्रीम सेट अप करें. Pager
क्लास में ऐसे मेथड होते हैं जो PagingSource
से PagingData
ऑब्जेक्ट की रिऐक्टिव स्ट्रीम दिखाते हैं. पेजिंग लाइब्रेरी में कई तरह की स्ट्रीम का इस्तेमाल किया जा सकता है. इनमें RxJava की Flow
, LiveData
, Flowable
, और Observable
टाइप की स्ट्रीम शामिल हैं.
रिऐक्टिव स्ट्रीम सेट अप करने के लिए Pager
इंस्टेंस बनाते समय, आपको इंस्टेंस के साथ एक PagingConfig
कॉन्फ़िगरेशन ऑब्जेक्ट और एक फ़ंक्शन देना होगा. यह फ़ंक्शन Pager
को बताता है कि PagingSource
लागू करने का इंस्टेंस कैसे पाया जाए:
Kotlin
val flow = Pager( // Configure how data is loaded by passing additional properties to // PagingConfig, such as prefetchDistance. PagingConfig(pageSize = 20) ) { ExamplePagingSource(backend, query) }.flow .cachedIn(viewModelScope)
Java
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact. CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel); Pager<Integer, User> pager = Pager<>( new PagingConfig(/* pageSize = */ 20), () -> ExamplePagingSource(backend, query)); Flowable<PagingData<User>> flowable = PagingRx.getFlowable(pager); PagingRx.cachedIn(flowable, viewModelScope);
Java
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact. CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel); Pager<Integer, User> pager = Pager<>( new PagingConfig(/* pageSize = */ 20), () -> ExamplePagingSource(backend, query)); PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);
cachedIn()
ऑपरेटर, डेटा स्ट्रीम को शेयर करने लायक बनाता है और दिए गए CoroutineScope
के साथ लोड किए गए डेटा को कैश मेमोरी में सेव करता है. इस उदाहरण में, लाइफ़साइकल lifecycle-viewmodel-ktx
आर्टफ़ैक्ट से मिले viewModelScope
का इस्तेमाल किया गया है.
Pager
ऑब्जेक्ट, PagingSource
ऑब्जेक्ट से load()
तरीके को कॉल करता है. साथ ही, उसे LoadParams
ऑब्जेक्ट देता है और बदले में LoadResult
ऑब्जेक्ट पाता है.
RecyclerView अडैप्टर तय करना
आपको अपनी RecyclerView
सूची में डेटा पाने के लिए, एक अडैप्टर भी सेट अप करना होगा. पेजिंग लाइब्रेरी, इस काम के लिए PagingDataAdapter
क्लास उपलब्ध कराती है.
PagingDataAdapter
को एक्सटेंड करने वाली क्लास तय करें. उदाहरण में, UserAdapter
, User
टाइप के सूची आइटम के लिए RecyclerView
एडेप्टर उपलब्ध कराने के लिए PagingDataAdapter
को एक्सटेंड़ करता है. साथ ही, UserViewHolder
का इस्तेमाल व्यू होल्डर के तौर पर करता है:
Kotlin
class UserAdapter(diffCallback: DiffUtil.ItemCallback<User>) : PagingDataAdapter<User, UserViewHolder>(diffCallback) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): UserViewHolder { return UserViewHolder(parent) } override fun onBindViewHolder(holder: UserViewHolder, position: Int) { val item = getItem(position) // Note that item can be null. ViewHolder must support binding a // null item as a placeholder. holder.bind(item) } }
Java
class UserAdapter extends PagingDataAdapter<User, UserViewHolder> { UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) { super(diffCallback); } @NonNull @Override public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new UserViewHolder(parent); } @Override public void onBindViewHolder(@NonNull UserViewHolder holder, int position) { User item = getItem(position); // Note that item can be null. ViewHolder must support binding a // null item as a placeholder. holder.bind(item); } }
Java
class UserAdapter extends PagingDataAdapter<User, UserViewHolder> { UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) { super(diffCallback); } @NonNull @Override public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new UserViewHolder(parent); } @Override public void onBindViewHolder(@NonNull UserViewHolder holder, int position) { User item = getItem(position); // Note that item can be null. ViewHolder must support binding a // null item as a placeholder. holder.bind(item); } }
आपके अडैप्टर को onCreateViewHolder()
और
onBindViewHolder()
तरीके भी तय करने होंगे. साथ ही, DiffUtil.ItemCallback
की जानकारी भी देनी होगी.
यह उसी तरह काम करता है जिस तरह RecyclerView
सूची के अडैप्टर तय करते समय होता है:
Kotlin
object UserComparator : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean { // Id is unique. return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { return oldItem == newItem } }
Java
class UserComparator extends DiffUtil.ItemCallback<User> { @Override public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) { // Id is unique. return oldItem.id.equals(newItem.id); } @Override public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem.equals(newItem); } }
Java
class UserComparator extends DiffUtil.ItemCallback<User> { @Override public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) { // Id is unique. return oldItem.id.equals(newItem.id); } @Override public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem.equals(newItem); } }
अपने यूज़र इंटरफ़ेस (यूआई) में पेज किया गया डेटा दिखाना
अब आपने PagingSource
तय कर लिया है, अपने ऐप्लिकेशन के लिए PagingData
की स्ट्रीम जनरेट करने का तरीका तय कर लिया है, और PagingDataAdapter
तय कर लिया है. अब आपके पास इन एलिमेंट को एक-दूसरे से कनेक्ट करने और अपनी गतिविधि में पेज किया गया डेटा दिखाने का विकल्प है.
अपनी गतिविधि के onCreate
या फ़्रैगमेंट के
onViewCreated
तरीके में, यह तरीका अपनाएं:
- अपनी
PagingDataAdapter
क्लास का एक इंस्टेंस बनाएं. PagingDataAdapter
इंस्टेंस को उसRecyclerView
सूची में पास करें जिसमें आपको पेज किया गया डेटा दिखाना है.PagingData
स्ट्रीम को देखें और जनरेट की गई हर वैल्यू को अपने एडेप्टर केsubmitData()
तरीके पर पास करें.
Kotlin
val viewModel by viewModels<ExampleViewModel>() val pagingAdapter = UserAdapter(UserComparator) val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) recyclerView.adapter = pagingAdapter // Activities can use lifecycleScope directly; fragments use // viewLifecycleOwner.lifecycleScope. lifecycleScope.launch { viewModel.flow.collectLatest { pagingData -> pagingAdapter.submitData(pagingData) } }
Java
ExampleViewModel viewModel = new ViewModelProvider(this) .get(ExampleViewModel.class); UserAdapter pagingAdapter = new UserAdapter(new UserComparator()); RecyclerView recyclerView = findViewById<RecyclerView>( R.id.recycler_view); recyclerView.adapter = pagingAdapter viewModel.flowable // Using AutoDispose to handle subscription lifecycle. // See: https://github.com/uber/AutoDispose. .to(autoDisposable(AndroidLifecycleScopeProvider.from(this))) .subscribe(pagingData -> pagingAdapter.submitData(lifecycle, pagingData));
Java
ExampleViewModel viewModel = new ViewModelProvider(this) .get(ExampleViewModel.class); UserAdapter pagingAdapter = new UserAdapter(new UserComparator()); RecyclerView recyclerView = findViewById<RecyclerView>( R.id.recycler_view); recyclerView.adapter = pagingAdapter // Activities can use getLifecycle() directly; fragments use // getViewLifecycleOwner().getLifecycle(). viewModel.liveData.observe(this, pagingData -> pagingAdapter.submitData(getLifecycle(), pagingData));
RecyclerView
सूची में अब डेटा सोर्स का पेज किया गया डेटा दिखता है. साथ ही, ज़रूरत पड़ने पर, यह अपने-आप दूसरा पेज लोड करता है.
अन्य संसाधन
पेजिंग लाइब्रेरी के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें:
कोडलैब
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- नेटवर्क और डेटाबेस से पेज
- पेजिंग 3 पर माइग्रेट करना
- पेजिंग लाइब्रेरी के बारे में खास जानकारी