Yükleme durumlarını yönetme ve sunma (Görünümler)

Kavramlar ve Jetpack Compose uygulaması

Paging kitaplığı, sayfalandırılmış veriler için yükleme isteklerinin durumunu izler ve LoadState sınıfı aracılığıyla kullanıma sunar. Uygulamanız, mevcut durumla ilgili bilgi almak ve kullanıcı arayüzünü buna göre güncellemek için PagingDataAdapter ile bir dinleyici kaydedebilir. Bu durumlar, kullanıcı arayüzüyle eşzamanlı oldukları için bağdaştırıcıdan sağlanır. Bu, dinleyicinizin sayfa yükleme işlemi kullanıcı arayüzüne uygulandığında güncellemeler alacağı anlamına gelir.

Her LoadState ve veri kaynağı türü (LoadType veya PagingSource) için ayrı bir RemoteMediator sinyali sağlanır. Dinleyici tarafından sağlanan CombinedLoadStates nesnesi, bu sinyallerin tümünden yükleme durumu hakkında bilgi sağlar. Bu ayrıntılı bilgileri kullanarak kullanıcılarınıza uygun yükleme göstergelerini gösterebilirsiniz.

Yükleme durumları

Paging kitaplığı, LoadState nesnesi aracılığıyla kullanıcı arayüzünde kullanılmak üzere yükleme durumunu gösterir. LoadState nesneleri, mevcut yükleme durumuna bağlı olarak üç biçimden birini alır:

Kullanıcı arayüzünüzde LoadState kullanmanın iki yolu vardır: bir dinleyici kullanma veya yükleme durumunu doğrudan RecyclerView listesinde göstermek için özel bir liste bağdaştırıcısı kullanma.

Yükleme durumuna bir dinleyiciyle erişme

Kullanıcı arayüzünüzde genel kullanım için yükleme durumunu almak üzere PagingDataAdapter tarafından sağlanan loadStateFlow akışını veya addLoadStateListener() yöntemini kullanın. Bu mekanizmalar, her yük türü için LoadState davranışı hakkında bilgi içeren bir CombinedLoadStates nesnesine erişim sağlar.

Aşağıdaki örnekte, PagingDataAdapter, yenileme yükünün mevcut durumuna bağlı olarak farklı kullanıcı arayüzü bileşenleri gösterir:

Kotlin

// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    progressBar.isVisible = loadStates.refresh is LoadState.Loading
    retry.isVisible = loadState.refresh !is LoadState.Loading
    errorMsg.isVisible = loadState.refresh is LoadState.Error
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

CombinedLoadStates hakkında daha fazla bilgi için Ek yükleme durumu bilgilerine erişme başlıklı makaleyi inceleyin.

Yükleme durumunu bir bağdaştırıcıyla sunma

Paging kitaplığı, yükleme durumunu doğrudan sayfalandırılmış verilerin gösterilen listesinde sunmak amacıyla LoadStateAdapter adlı başka bir liste bağdaştırıcısı sağlar. Bu bağdaştırıcı, listenin mevcut yükleme durumuna erişim sağlar. Bu durumu, bilgileri görüntüleyen özel bir görünüm tutucusuna iletebilirsiniz.

Öncelikle, ekranınızdaki yükleme ve hata görünümlerine referansları tutan bir görünüm tutucu sınıfı oluşturun. bind() işlevi oluşturun. Bu işlev, parametre olarak LoadState kabul eder. Bu işlev, yükleme durumu parametresine göre görünümün görünürlüğünü açıp kapatmalıdır:

Kotlin

class LoadStateViewHolder(
  parent: ViewGroup,
  retry: () -> Unit
) : RecyclerView.ViewHolder(
  LayoutInflater.from(parent.context)
    .inflate(R.layout.load_state_item, parent, false)
) {
  private val binding = LoadStateItemBinding.bind(itemView)
  private val progressBar: ProgressBar = binding.progressBar
  private val errorMsg: TextView = binding.errorMsg
  private val retry: Button = binding.retryButton
    .also {
      it.setOnClickListener { retry() }
    }

  fun bind(loadState: LoadState) {
    if (loadState is LoadState.Error) {
      errorMsg.text = loadState.error.localizedMessage
    }

    progressBar.isVisible = loadState is LoadState.Loading
    retry.isVisible = loadState is LoadState.Error
    errorMsg.isVisible = loadState is LoadState.Error
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

Ardından, LoadStateAdapter uygulayan bir sınıf oluşturun ve onCreateViewHolder() ve onBindViewHolder() yöntemlerini tanımlayın. Bu yöntemler, özel görünüm tutucunuzun bir örneğini oluşturur ve ilişkili yükleme durumunu bağlar.

Kotlin

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
  private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {

  override fun onCreateViewHolder(
    parent: ViewGroup,
    loadState: LoadState
  ) = LoadStateViewHolder(parent, retry)

  override fun onBindViewHolder(
    holder: LoadStateViewHolder,
    loadState: LoadState
  ) = holder.bind(loadState)
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

Yükleme ilerleme durumunu bir üstbilgi ve altbilgide göstermek için withLoadStateHeaderAndFooter() yöntemini PagingDataAdapter nesnenizden çağırın:

Kotlin

pagingAdapter
  .withLoadStateHeaderAndFooter(
    header = ExampleLoadStateAdapter(adapter::retry),
    footer = ExampleLoadStateAdapter(adapter::retry)
  )

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

RecyclerView listesinin yükleme durumunu yalnızca üstbilgide veya yalnızca altbilgide göstermek istiyorsanız bunun yerine withLoadStateHeader() veya withLoadStateFooter() işlevini çağırabilirsiniz.

Ek yükleme durumu bilgilerine erişme

PagingDataAdapter öğesindeki CombinedLoadStates nesnesi, PagingSource uygulamanızın ve varsa RemoteMediator uygulamanızın yükleme durumları hakkında bilgi sağlar.

Kolaylık sağlaması açısından, uygun yük türü için bir LoadState nesnesine erişmek üzere CombinedLoadStates'deki refresh, append ve prepend özelliklerini kullanabilirsiniz. Bu özellikler genellikle varsa RemoteMediator uygulamasındaki yükleme durumunu kullanır. Aksi takdirde, PagingSource uygulamasındaki uygun yükleme durumunu içerirler. Temel mantık hakkında daha ayrıntılı bilgi için CombinedLoadStates ile ilgili referans belgelerine bakın.

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Observe refresh load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    refreshLoadState: LoadState = loadStates.refresh
    // Observe prepend load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    prependLoadState: LoadState = loadStates.prepend
    // Observe append load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    appendLoadState: LoadState = loadStates.append
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

Ancak yalnızca PagingSourceyükleme durumlarının kullanıcı arayüzü güncellemeleriyle senkronize olacağı garanti edilir. refresh, append ve prepend özellikleri yükleme durumunu PagingSource veya RemoteMediator'den alabileceğinden, kullanıcı arayüzü güncellemeleriyle senkronize olmaları garanti edilmez. Bu durum, yeni verilerin kullanıcı arayüzüne eklenmesinden önce yükleme işleminin tamamlanmış gibi görünmesine neden olan kullanıcı arayüzü sorunlarına yol açabilir.

Bu nedenle, kolaylık erişimcileri yükleme durumunu bir üstbilgi veya altbilgide göstermek için uygundur ancak diğer kullanım alanlarında yükleme durumuna PagingSource veya RemoteMediator üzerinden özel olarak erişmeniz gerekebilir. CombinedLoadStates bu amaçla source ve mediator özelliklerini sağlar. Bu özelliklerin her biri, sırasıyla PagingSource veya RemoteMediator için LoadState nesnelerini içeren bir LoadStates nesnesi gösterir:

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Directly access the RemoteMediator refresh load state.
    mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh
    // Directly access the RemoteMediator append load state.
    mediatorAppendLoadState: LoadState? = loadStates.mediator.append
    // Directly access the RemoteMediator prepend load state.
    mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend
    // Directly access the PagingSource refresh load state.
    sourceRefreshLoadState: LoadState = loadStates.source.refresh
    // Directly access the PagingSource append load state.
    sourceAppendLoadState: LoadState = loadStates.source.append
    // Directly access the PagingSource prepend load state.
    sourcePrependLoadState: LoadState = loadStates.source.prepend
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

LoadState'teki zincir operatörleri

CombinedLoadStates nesnesi, yükleme durumundaki tüm değişikliklere erişim sağladığından yükleme durumu akışını belirli etkinliklere göre filtrelemek önemlidir. Bu, kullanıcı arayüzünüzü uygun zamanda güncelleyerek takılmaları ve gereksiz kullanıcı arayüzü güncellemelerini önlemenizi sağlar.

Örneğin, boş bir görünüm görüntülemek istediğinizi ancak yalnızca ilk veri yükleme işlemi tamamlandıktan sonra görüntülemek istediğinizi varsayalım. Bu kullanım alanında, veri yenileme yüklemesinin başladığını doğrulamanız ve ardından yenilemenin tamamlandığını onaylamak için NotLoading durumunu beklemeniz gerekir. İhtiyacınız olanlar hariç tüm sinyalleri filtrelemeniz gerekir:

Kotlin

lifecycleScope.launchWhenCreated {
  adapter.loadStateFlow
    // Only emit when REFRESH LoadState for RemoteMediator changes.
    .distinctUntilChangedBy { it.refresh }
    // Only react to cases where REFRESH completes, such as NotLoading.
    .filter { it.refresh is LoadState.NotLoading }
    // Scroll to top is synchronous with UI updates, even if remote load was
    // triggered.
    .collect { binding.list.scrollToPosition(0) }
}

Java

PublishSubject<CombinedLoadStates> subject = PublishSubject.create();
Disposable disposable =
  subject.distinctUntilChanged(CombinedLoadStates::getRefresh)
  .filter(
    combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading)
  .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0));

pagingAdapter.addLoadStateListener(loadStates -> {
  subject.onNext(loadStates);
});

Java

LiveData<CombinedLoadStates> liveData = new MutableLiveData<>();
LiveData<LoadState> refreshLiveData =
  Transformations.map(liveData, CombinedLoadStates::getRefresh);
LiveData<LoadState> distinctLiveData =
  Transformations.distinctUntilChanged(refreshLiveData);

distinctLiveData.observeForever(loadState -> {
  if (loadState instanceof LoadState.NotLoading) {
    binding.list.scrollToPosition(0);
  }
});

Bu örnek, yenileme yükleme durumu güncellenene kadar bekler ancak yalnızca durum NotLoading olduğunda tetiklenir. Bu, uzaktan yenilemenin tamamen bitmesini sağlar. Böylece kullanıcı arayüzünde herhangi bir güncelleme yapılmadan önce yenileme işlemi tamamlanır.

Stream API'ler bu tür bir işlemi mümkün kılar. Uygulamanız, ihtiyaç duyduğu yükleme etkinliklerini belirtebilir ve uygun ölçütler karşılandığında yeni verileri işleyebilir.