کتابخانه Paging قابلیت های قدرتمندی را برای بارگیری و نمایش داده های صفحه بندی شده از مجموعه داده های بزرگتر فراهم می کند. این راهنما نحوه استفاده از کتابخانه Paging را برای تنظیم جریانی از داده های صفحه بندی شده از منبع داده شبکه و نمایش آن در RecyclerView
نشان می دهد.
یک منبع داده را تعریف کنید
اولین گام، تعریف یک پیاده سازی PagingSource
برای شناسایی منبع داده است. کلاس PagingSource
API شامل متد load()
است که برای نشان دادن نحوه بازیابی داده های صفحه شده از منبع داده مربوطه، آن را باطل می کنید.
از کلاس PagingSource
مستقیماً برای استفاده از کوروتین های Kotlin برای بارگذاری همگام استفاده کنید. کتابخانه Paging همچنین کلاس هایی را برای پشتیبانی از سایر چارچوب های همگام ارائه می دهد:
- برای استفاده از RxJava، به جای آن
RxPagingSource
پیاده سازی کنید. - برای استفاده از
ListenableFuture
از Guava، به جای آنListenableFuturePagingSource
را پیاده سازی کنید.
انواع کلید و مقدار را انتخاب کنید
PagingSource<Key, Value>
دارای دو نوع پارامتر است: Key
و Value
. کلید شناسه مورد استفاده برای بارگذاری داده را تعریف می کند و مقدار آن نوع خود داده است. به عنوان مثال، اگر صفحاتی از اشیاء User
را از شبکه با ارسال شماره صفحات Int
به Retrofit بارگیری می کنید، Int
به عنوان نوع Key
و User
به عنوان نوع Value
انتخاب کنید.
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> { 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) } } }
جاوا
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; } }
جاوا
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
: نمونه ای از سرویس Backend که داده ها را فراهم می کند -
query
: عبارت جستجو برای ارسال به سرویس نشان داده شده توسطbackend
شی LoadParams
حاوی اطلاعاتی درباره عملیات بارگذاری است که باید انجام شود. این شامل کلیدی است که باید بارگذاری شود و تعداد مواردی که باید بارگذاری شوند.
شی LoadResult
حاوی نتیجه عملیات بارگذاری است. LoadResult
یک کلاس مهر و موم شده است که بسته به موفقیت آمیز بودن فراخوانی load()
یکی از دو شکل را دارد:
- اگر بارگیری موفقیت آمیز بود، یک شی
LoadResult.Page
را برگردانید. - اگر بارگیری موفقیت آمیز نبود، یک شی
LoadResult.Error
را برگردانید.
شکل زیر نشان می دهد که چگونه تابع load()
در این مثال کلید را برای هر بار دریافت می کند و کلید بار بعدی را ارائه می دهد.
پیاده سازی PagingSource
باید یک متد getRefreshKey()
را نیز پیاده سازی کند که یک شی PagingState
را به عنوان پارامتر می گیرد. هنگامی که داده ها پس از بارگذاری اولیه رفرش یا باطل می شوند، کلید را برای عبور به load()
برمی گرداند. کتابخانه صفحهبندی این روش را بهطور خودکار در بازخوانیهای بعدی دادهها فراخوانی میکند.
رسیدگی به خطاها
درخواستها برای بارگیری دادهها ممکن است به دلایلی با شکست مواجه شوند، بهویژه هنگام بارگیری از طریق شبکه. با برگرداندن یک شی LoadResult.Error
از متد load()
خطاهایی را که در حین بارگیری با آنها مواجه شده است گزارش کنید.
به عنوان مثال، می توانید با اضافه کردن موارد زیر به متد load()
خطاهای بارگذاری را در ExamplePagingSource
از مثال قبلی دریافت و گزارش کنید:
کاتلین
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) }
جاوا
return backend.searchUsers(searchTerm, nextPageNumber) .subscribeOn(Schedulers.io()) .map(this::toLoadResult) .onErrorReturn(LoadResult.Error::new);
جاوا
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
API مراجعه کنید.
PagingSource
اشیاء LoadResult.Error
را جمع آوری و به UI تحویل می دهد تا بتوانید بر اساس آنها عمل کنید. برای اطلاعات بیشتر در مورد نمایش وضعیت بارگیری در رابط کاربری، به مدیریت و ارائه وضعیت های بارگیری مراجعه کنید.
یک جریان از PagingData را تنظیم کنید
در مرحله بعد، به جریانی از داده های صفحه بندی شده از پیاده سازی PagingSource
نیاز دارید. جریان داده را در ViewModel
خود تنظیم کنید. کلاس Pager
متدهایی را ارائه می دهد که یک جریان واکنشی از اشیاء PagingData
را از یک PagingSource
نشان می دهد. کتابخانه Paging از چندین نوع جریان، از جمله Flow
، LiveData
، و انواع Flowable
و Observable
از RxJava پشتیبانی می کند.
هنگامی که یک نمونه Pager
برای تنظیم جریان واکنشی خود ایجاد می کنید، باید نمونه را با یک شی پیکربندی PagingConfig
و یک تابع ارائه کنید که Pager
می گوید چگونه یک نمونه از پیاده سازی PagingSource
خود را دریافت کند:
کاتلین
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)
جاوا
// 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);
جاوا
// 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
ارائه شده ذخیره می کند. این مثال از viewModelScope
ارائه شده توسط lifecycle lifecycle-viewmodel-ktx
استفاده می کند.
شی Pager
متد load()
را از شی PagingSource
فراخوانی می کند و شیء LoadParams
را به آن ارائه می دهد و در عوض شی LoadResult
دریافت می کند.
یک آداپتور RecyclerView را تعریف کنید
همچنین باید یک آداپتور برای دریافت داده ها در لیست RecyclerView
خود تنظیم کنید. کتابخانه Paging کلاس PagingDataAdapter
را برای این منظور فراهم می کند.
کلاسی را تعریف کنید که PagingDataAdapter
را گسترش دهد. در مثال، UserAdapter
PagingDataAdapter
را گسترش میدهد تا یک آداپتور RecyclerView
را برای آیتمهای لیست از نوع User
و با استفاده از UserViewHolder
بهعنوان نگهدارنده نمایش ارائه کند:
کاتلین
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) } }
جاوا
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); } }
جاوا
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
انجام می شود:
کاتلین
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 } }
جاوا
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); } }
جاوا
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); } }
داده های صفحه شده را در UI خود نمایش دهید
اکنون که یک PagingSource
تعریف کردهاید، راهی برای برنامهتان ایجاد کردهاید تا جریانی از PagingData
تولید کند، و یک PagingDataAdapter
تعریف کردهاید، آمادهاید تا این عناصر را به یکدیگر متصل کنید و دادههای صفحهشده را در فعالیت خود نمایش دهید.
مراحل زیر را در روش onCreate
یا قطعه onViewCreated
در فعالیت خود انجام دهید:
- یک نمونه از کلاس
PagingDataAdapter
خود ایجاد کنید. - نمونه
PagingDataAdapter
را به لیستRecyclerView
که میخواهید دادههای صفحهشدهتان را نمایش دهد، ارسال کنید. - جریان
PagingData
را مشاهده کنید و هر مقدار تولید شده را به متدsubmitData()
آداپتور خود ارسال کنید.
کاتلین
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) } }
جاوا
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));
جاوا
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
اکنون داده های صفحه شده را از منبع داده نمایش می دهد و در صورت لزوم صفحه دیگری را به طور خودکار بارگیری می کند.
منابع اضافی
برای کسب اطلاعات بیشتر در مورد کتابخانه Paging، به منابع اضافی زیر مراجعه کنید:
Codelabs
نمونه ها
{% کلمه به کلمه %}برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- صفحه از شبکه و پایگاه داده
- مهاجرت به صفحه 3
- نمای کلی کتابخانه صفحهبندی