Sayfalandırma kitaplığı, yükleme ve görüntüleme için güçlü özellikler sunar.
sayfalandırılmış verileri büyük bir veri kümesinden alır. Bu kılavuzda, sayfalandırmanın nasıl kullanılacağı gösterilmektedir
ağ veri kaynağından sayfalandırılmış veri akışını ayarlamak için kullanılan
RecyclerView
içinde olması gerekir.
Veri kaynağı tanımlayın
İlk adım, hedef kitlenizin
PagingSource
uygulaması
veri kaynağını tanımlamaktır. PagingSource
API sınıfı şunları içerir:
load()
yöntemini çağırın. Bu yöntemi, sayfalandırılmış verilerin
karşılık gelen veri kaynağında bulunabilir.
Eş zamansız için Kotlin eş yordamlarını kullanmak üzere doğrudan PagingSource
sınıfını kullanın
yükleniyor. Sayfalandırma kitaplığı, diğer eş zamansız eşlemeyi destekleyen sınıflar da sunar.
çerçeveler:
- RxJava kullanmak için
RxPagingSource
. - Guava'dan
ListenableFuture
kullanmak için şunu uygulayın:ListenableFuturePagingSource
.
Anahtar ve değer türlerini seçin
PagingSource<Key, Value>
, iki tür parametresine sahiptir: Key
ve Value
. Temel
verileri yüklemek için kullanılan tanımlayıcıyı tanımlar ve değer,
verilerdir. Örneğin, ağdan User
nesnenin sayfalarını yüklerseniz
Int
sayfa numarasını
Geriye dönük
Key
türü olarak Int
, Value
türü olarak User
seçeneğini belirleyin.
PagingSource'u (Sayfa Kaynağı) tanımlayın
Aşağıdaki örnekte
Yüklenen PagingSource
sayfa numarasına göre öğe sayfaları oluşturabilirsiniz. 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ı,
oluşturucuyu load()
yöntemini kullanarak bir sorgu için uygun verileri yüklemektir.
Bu parametreler şunlardır:
backend
: verileri sağlayan arka uç hizmetinin örneğiquery
:backend
ile belirtilen hizmete gönderilecek arama sorgusu
LoadParams
nesnesi, gerçekleştirilecek yükleme işlemiyle ilgili bilgiler içerir. Bu
yüklenecek anahtarı ve yüklenecek öğe sayısını içerir.
LoadResult
nesnesi, yükleme işleminin sonucunu içerir. LoadResult
mühürlü bir sınıf
şu iki biçimden birini alır: load()
aramasının başarılı olup olmadığına bağlı olarak:
- Yükleme başarılı olursa bir
LoadResult.Page
nesnesi döndürün. - Yükleme başarılı olmazsa bir
LoadResult.Error
nesnesi döndürün.
Aşağıdaki şekilde, bu örnekteki load()
işlevinin nasıl çalıştığı gösterilmektedir
her yük için anahtarı alır ve sonraki yükleme için anahtarı sağlar.
load()
hizmetinin anahtarı nasıl kullandığını ve güncellediğini gösteren şema.
PagingSource
uygulaması,
getRefreshKey()
Sprint planlaması ve
PagingState
nesnesi
parametresinden sonra bir değer girin. Veriler olduğunda load()
yöntemine geçirilecek anahtarı döndürür
sonra yenilenmiş veya geçersiz kılınmıştır. Sayfalandırma Kitaplığı'nda bu ad verilir.
yöntemini otomatik olarak kullanır.
Hataları işleme
Veri yükleme istekleri, özellikle yükleme sırasında birçok nedenden dolayı başarısız olabilir
görüntüleyebilirsiniz. Yükleme sırasında
load()
yönteminden LoadResult.Error
nesnesi.
Örneğin, ExamplePagingSource
komut dosyasında yükleme hatalarını yakalayıp bildirebilirsiniz
load()
yöntemine aşağıdaki ifadeyi ekleyerek önceki örnekten itibaren:
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ın ele alınması hakkında daha fazla bilgi için
PagingSource
API referansı.
PagingSource
, LoadResult.Error
nesneyi toplayıp kullanıcı arayüzüne gönderir. Böylece
harekete geçmenizi öneririz. Yükleme durumunu gösterme hakkında daha fazla bilgi
bölümünde Yüklemeyi yönetme ve gösterme
durumlarına bakın.
PagingData akışı ayarlama
Ardından, PagingSource
uygulamasından sayfalandırılmış veri akışına ihtiyacınız vardır.
Veri akışını ViewModel
cihazınızda ayarlayın. İlgili içeriği oluşturmak için kullanılan
Pager
sınıfı, aşağıdakileri sağlayan yöntemler sunar:
reaktif bir akışla
PagingData
nesne
PagingSource
. Sayfalandırma kitaplığı çeşitli akış türlerinin kullanılmasını destekler:
Flow
, LiveData
ve Flowable
ile Observable
türleri dahil
RxJava.
Reaktif akışınızı ayarlamak için Pager
örneği oluşturduğunuzda
örneğe bunun için
PagingConfig
yapılandırması
Pager
nesnesine ait örneğinizin nasıl alınacağını bildiren bir işlev
PagingSource
uygulaması:
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 akışı önbelleğe alır
sağlanan CoroutineScope
öğesine sahip veriler. Bu örnekte viewModelScope
kullanılmıştır
lifecycle-viewmodel-ktx
yaşam döngüsü yapısı tarafından sağlanır.
Pager
nesnesi, PagingSource
nesnesinden load()
yöntemini çağırır
Bunu sağlamak için
LoadParams
nesne
ve
LoadResult
nesne
karşılığında.
RecyclerView adaptörü tanımlama
Verileri RecyclerView
cihazınıza almak için bir adaptör kurmanız da gerekir.
liste'ye dokunun. Sayfalama kitaplığı bunun için PagingDataAdapter
sınıfını sağlar
amaçlanıyor.
PagingDataAdapter
öğesini genişleten bir sınıf tanımlayın. Örnekte,
UserAdapter
, PagingDataAdapter
uzatarak RecyclerView
sağlar
User
türündeki liste öğeleri için adaptör ve görünüm olarak UserViewHolder
kullanan
sahibi:
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); } }
Bağdaştırıcınız onCreateViewHolder()
ve
onBindViewHolder()
yöntem ve bir değer belirtin
DiffUtil.ItemCallback
.
Bu süreç, RecyclerView
listesi tanımlanırken olduğu gibi çalışır.
adaptörler:
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ünde görüntüleyin
Artık bir PagingSource
tanımladığınıza göre, uygulamanızın
bir PagingData
akışı oluşturuyor ve bir PagingDataAdapter
tanımladıysanız
bu öğeleri birbirine bağlamaya ve sayfalandırılmış verileri
etkinliği'ne dokunun.
Etkinliğinizin onCreate
veya parçanın öğesinde aşağıdaki adımları uygulayın
onViewCreated
yöntemi:
PagingDataAdapter
sınıfınızın bir örneğini oluşturun.PagingDataAdapter
örneğini şuraya geçirin:RecyclerView
sayfalandırılmış verilerinizi görüntülemek istediğiniz bir liste seçin.PagingData
akışını inceleyin ve oluşturulan her değeri bağdaştırıcınınsubmitData()
yöntemini kullanır.
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
listesinde artık veri kaynağındaki sayfalandırılmış veriler görüntülenir ve
gerektiğinde başka bir sayfayı otomatik olarak yükler.
Ek kaynaklar
Sayfalama kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:
Codelab'ler
Örnekler
- Android Mimari Bileşenleri Sayfalandırması örnek
- Ağ ile Android Mimari Bileşenleri İlerleme Durumu örnek
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Ağ ve veritabanındaki sayfa
- Sayfa 3'e geçme
- Çağrı kitaplığına genel bakış