Sayfalı verileri yükleme ve görüntüleme

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:

ziyaret edin.

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ği
  • query: 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.

Her load() çağrısında, ExamplePagingSource geçerli anahtarı alır
    ve yüklemek için bir sonraki anahtara döner.
Şekil 1. 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:

  1. PagingDataAdapter sınıfınızın bir örneğini oluşturun.
  2. PagingDataAdapter örneğini şuraya geçirin: RecyclerView sayfalandırılmış verilerinizi görüntülemek istediğiniz bir liste seçin.
  3. PagingData akışını inceleyin ve oluşturulan her değeri bağdaştırıcının submitData() 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

ziyaret edin. ziyaret edin.