Sayfalama kitaplığı, daha büyük bir veri kümesinden sayfalı verileri yüklemek ve görüntülemek için güçlü özellikler sunar. Bu kılavuzda, bir ağ veri kaynağından sayfalı veri akışı oluşturmak ve bunu bir RecyclerView içinde görüntülemek için Sayfalama kitaplığının nasıl kullanılacağı gösterilmektedir.
Veri kaynağı tanımlama
İlk adım, veri kaynağını tanımlamak için bir PagingSource uygulaması tanımlamaktır. PagingSource API sınıfı, sayfalandırılmış verilerin ilgili veri kaynağından nasıl alınacağını belirtmek için geçersiz kıldığınız load() yöntemini içerir.
Asynchronize yükleme için Kotlin coroutine'lerini kullanmak istiyorsanız doğrudan PagingSource sınıfını kullanın. Sayfalama kitaplığı, diğer asenkron çerçeveleri desteklemek için sınıflar da sağlar:
- RxJava kullanmak için bunun yerine
RxPagingSourceuygulamasını kullanın. - Guava'dan
ListenableFuturekullanmak için bunun yerineListenableFuturePagingSourcesınıfını uygulayın.
Anahtar ve değer türlerini seçme
PagingSource<Key, Value> iki tür parametresi vardır: Key ve Value. Anahtar, verileri yüklemek için kullanılan tanımlayıcıyı tanımlar ve değer, verilerin türüdür. Örneğin, Int sayfa numaralarını Retrofit'e ileterek ağdan User nesnelerinin sayfalarını yüklüyorsanız Key türü olarak Int'ü, Value türü olarak da User'ü seçin.
PagingSource'ı tanımlama
Aşağıdaki örnekte, sayfa numarasına göre öğe sayfalarını yükleyen bir PagingSource uygulanmaktadır. Key türü Int, Value türü ise 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; } }
Tipik bir PagingSource uygulaması, bir sorgu için uygun verileri yüklemek üzere yapıcısında sağlanan parametreleri load() yöntemine iletir. Yukarıdaki örnekte bu parametreler şunlardır:
backend: Verileri sağlayan arka uç hizmetinin bir örneğiquery:backendile belirtilen hizmete gönderilecek arama sorgusu
LoadParams
nesnesi, gerçekleştirilecek yükleme işlemiyle ilgili bilgileri içerir. Bu, yüklenecek anahtarı ve yüklenecek öğelerin sayısını içerir.
LoadResult nesnesi, yükleme işleminin sonucunu içerir. LoadResult, load() çağrısının başarılı olup olmadığına bağlı olarak iki biçimden birini alan kapalı bir sınıftır:
- Yükleme başarılı olursa bir
LoadResult.Pagenesnesi döndürülür. - Yükleme başarılı olmazsa bir
LoadResult.Errornesnesi döndürün.
Aşağıdaki şekilde, bu örnekteki load() işlevinin her yükleme için anahtarı nasıl aldığı ve sonraki yükleme için anahtarı nasıl sağladığı gösterilmektedir.
load()'ün anahtarı nasıl kullandığını ve güncellediğini gösteren şema.
PagingSource uygulaması, parametre olarak PagingState nesnesi alan bir getRefreshKey() yöntemi de uygulamalıdır. İlk yükleme işleminden sonra veriler yenilendiğinde veya geçersiz olduğunda load() yöntemine iletilecek anahtarı döndürür. Sayfalama kitaplığı, verilerin sonraki yenilemelerinde bu yöntemi otomatik olarak çağırır.
Hataları işleme
Veri yükleme istekleri, özellikle ağ üzerinden yükleme yapılırken çeşitli nedenlerle başarısız olabilir. load() yönteminden bir LoadResult.Error nesnesi döndürerek yükleme sırasında karşılaşılan hataları bildirin.
Örneğin, önceki örnekteki ExamplePagingSource öğesinde yükleme hatalarını yakalayıp bildirmek için load() yöntemine aşağıdakileri ekleyebilirsiniz:
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 hatalarını ele alma hakkında daha fazla bilgi için PagingSource API referansında yer alan örneklere bakın.
PagingSource, LoadResult.Error nesnelerini toplayıp kullanıcı arayüzüne gönderir. Böylece bu nesnelerle ilgili işlem yapabilirsiniz. Yükleme durumunu kullanıcı arayüzünde gösterme hakkında daha fazla bilgi için Yükleme durumlarını yönetme ve gösterme başlıklı makaleyi inceleyin.
PagingData akışı oluşturma
Ardından, PagingSource uygulamasından sayfalı bir veri akışına ihtiyacınız vardır.
ViewModel'ünüzde veri akışını ayarlayın. Pager sınıfı, PagingSource kaynağından PagingData nesnelerinin reaktif akışını gösteren yöntemler sağlar. Sayfalama kitaplığı, Flow, LiveData ve RxJava'daki Flowable ve Observable türleri dahil olmak üzere çeşitli akış türlerinin kullanılmasını destekler.
Reaktif akışınızı ayarlamak için bir Pager örneği oluşturduğunuzda, örneğe bir PagingConfig yapılandırma nesnesi ve Pager'e PagingSource uygulamanızın bir örneğini nasıl alacağını bildiren bir işlev sağlamanız gerekir:
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() operatörü, veri akışını paylaşılabilir hale getirir ve yüklenen verileri sağlanan CoroutineScope ile önbelleğe alır. Bu örnekte, yaşam döngüsü lifecycle-viewmodel-ktx yapısını sağlayan viewModelScope kullanılmaktadır.
Pager nesnesi, PagingSource nesnesinden load() yöntemini çağırır, ona LoadParams nesnesini sağlar ve LoadResult nesnesini alır.
RecyclerView adaptörü tanımlama
Ayrıca, verileri RecyclerView listenize almak için bir adaptör de oluşturmanız gerekir. Sayfalama kitaplığı bu amaç için PagingDataAdapter sınıfını sağlar.
PagingDataAdapter sınıfını genişleten bir sınıf tanımlayın. Örnekte, UserAdapter, User türündeki liste öğeleri için bir RecyclerView bağdaştırıcısı sağlamak üzere PagingDataAdapter'ü genişletir ve UserViewHolder'i görüntü tutucusu olarak kullanır:
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); } }
Ayrıca, bağdaştırıcınız onCreateViewHolder() ve onBindViewHolder() yöntemlerini tanımlamalı ve bir DiffUtil.ItemCallback belirtmelidir.
Bu, RecyclerView liste bağdaştırıcıların tanımlanması sırasında normalde olduğu gibi çalışır:
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); } }
Sayfalandırılmış verileri kullanıcı arayüzünüzde görüntüleme
Bir PagingSource tanımladığınıza, uygulamanızın PagingData akışı oluşturması için bir yol oluşturduğunuza ve bir PagingDataAdapter tanımladığınıza göre bu öğeleri birbirine bağlamaya ve etkinliğinizde sayfalı verileri göstermeye hazırsınız.
Aktivitenizin onCreate veya fragment'inin onViewCreated yönteminde aşağıdaki adımları uygulayın:
PagingDataAdaptersınıfınızın bir örneğini oluşturun.PagingDataAdapterörneğini, sayfalı verilerinizi görüntülemek istediğinizRecyclerViewlistesine iletin.PagingDataakışını gözlemleyin ve oluşturulan her değeri bağdaştırıcınınsubmitData()yöntemine iletin.
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 listesi artık veri kaynağındaki sayfalı verileri gösterir ve gerektiğinde otomatik olarak başka bir sayfa yükler.
Ek kaynaklar
Sayfalama kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:
Codelab uygulamaları
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Ağı ve veritabanından sayfa
- 3. Sayfalamaya Geçiş
- Sayfa ayırma kitaplığına genel bakış