مدیریت و ارائه حالت‌های بارگذاری (Views)

مفاهیم و پیاده‌سازی Jetpack Compose

کتابخانه Paging وضعیت درخواست‌های بارگذاری برای داده‌های صفحه‌بندی‌شده را ردیابی می‌کند و آن را از طریق کلاس LoadState نمایش می‌دهد. برنامه شما می‌تواند یک شنونده (listener) را با PagingDataAdapter ثبت کند تا اطلاعاتی در مورد وضعیت فعلی دریافت کند و رابط کاربری (UI) را بر اساس آن به‌روزرسانی کند. این وضعیت‌ها از آداپتور ارائه می‌شوند زیرا با رابط کاربری (UI) همگام هستند. این بدان معناست که شنونده شما هنگامی که بارگذاری صفحه در رابط کاربری اعمال می‌شود، به‌روزرسانی‌ها را دریافت می‌کند.

برای هر نوع LoadType و منبع داده (اعم از PagingSource یا RemoteMediator ) یک سیگنال LoadState جداگانه ارائه می‌شود. شیء CombinedLoadStates که توسط شنونده ارائه می‌شود، اطلاعاتی در مورد وضعیت بارگذاری از همه این سیگنال‌ها ارائه می‌دهد. می‌توانید از این اطلاعات دقیق برای نمایش شاخص‌های بارگذاری مناسب به کاربران خود استفاده کنید.

حالت‌های بارگیری

کتابخانه Paging وضعیت بارگذاری را برای استفاده در رابط کاربری از طریق شیء LoadState نمایش می‌دهد. اشیاء LoadState بسته به وضعیت بارگذاری فعلی، یکی از سه شکل زیر را به خود می‌گیرند:

  • اگر هیچ عملیات بارگذاری فعال و خطایی وجود نداشته باشد، آنگاه LoadState یک شیء LoadState.NotLoading است. این زیرکلاس همچنین شامل ویژگی endOfPaginationReached است که نشان می‌دهد آیا به پایان صفحه‌بندی رسیده‌ایم یا خیر.
  • اگر یک عملیات بارگذاری فعال وجود داشته باشد، آنگاه LoadState یک شیء LoadState.Loading است.
  • اگر خطایی وجود داشته باشد، LoadState یک شیء LoadState.Error است.

دو راه برای استفاده از LoadState در رابط کاربری شما وجود دارد: استفاده از یک شنونده (listener) یا استفاده از یک آداپتور لیست ویژه برای نمایش مستقیم وضعیت بارگذاری در لیست RecyclerView .

دسترسی به وضعیت بارگذاری با یک شنونده

برای دریافت وضعیت بارگذاری برای استفاده عمومی در رابط کاربری خود، از جریان loadStateFlow یا متد addLoadStateListener() ارائه شده توسط PagingDataAdapter خود استفاده کنید. این مکانیزم‌ها دسترسی به یک شیء CombinedLoadStates را فراهم می‌کنند که شامل اطلاعاتی در مورد رفتار LoadState برای هر نوع بارگذاری است.

در مثال زیر، PagingDataAdapter بسته به وضعیت فعلی بارگذاری رفرش، اجزای رابط کاربری متفاوتی را نمایش می‌دهد:

کاتلین

// 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
  }
}

جاوا

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);
});

جاوا

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 ، به بخش «دسترسی به اطلاعات بیشتر در مورد وضعیت بارگذاری» مراجعه کنید.

وضعیت بارگیری را با یک آداپتور ارائه دهید

کتابخانه Paging یک آداپتور لیست دیگر به نام LoadStateAdapter ارائه می‌دهد تا وضعیت بارگذاری را مستقیماً در لیست نمایش داده شده از داده‌های صفحه‌بندی شده نمایش دهد. این آداپتور دسترسی به وضعیت بارگذاری فعلی لیست را فراهم می‌کند که می‌توانید آن را به یک نگهدارنده نمای سفارشی که اطلاعات را نمایش می‌دهد، منتقل کنید.

ابتدا، یک کلاس نگهدارنده‌ی نما (view holder) ایجاد کنید که ارجاعات به نماهای بارگذاری و خطا را روی صفحه نمایش شما نگه می‌دارد. یک تابع bind() ایجاد کنید که LoadState به عنوان پارامتر بپذیرد. این تابع باید قابلیت مشاهده‌ی نما را بر اساس پارامتر وضعیت بارگذاری تغییر دهد:

کاتلین

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
  }
}

جاوا

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);
  }
}

جاوا

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);
  }
}

در مرحله بعد، یک کلاس ایجاد کنید که LoadStateAdapter پیاده‌سازی کند و متدهای onCreateViewHolder() و onBindViewHolder() را تعریف کنید. این متدها یک نمونه از view holder سفارشی شما ایجاد می‌کنند و load state مرتبط را bind می‌کنند.

کاتلین

// 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)
}

جاوا

// 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);
  }
}

جاوا

// 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);
  }
}

برای نمایش پیشرفت بارگذاری در یک سرصفحه و یک پاصفحه، متد withLoadStateHeaderAndFooter() را از شیء PagingDataAdapter خود فراخوانی کنید:

کاتلین

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

جاوا

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

جاوا

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

اگر می‌خواهید لیست RecyclerView وضعیت بارگذاری را فقط در هدر یا فقط در فوتر نمایش دهد، می‌توانید از withLoadStateHeader() یا withLoadStateFooter() استفاده کنید.

دسترسی به اطلاعات وضعیت بارگذاری اضافی

شیء CombinedLoadStates از PagingDataAdapter اطلاعاتی در مورد وضعیت بارگذاری برای پیاده‌سازی PagingSource و همچنین برای پیاده‌سازی RemoteMediator شما، در صورت وجود، ارائه می‌دهد.

برای راحتی، می‌توانید از ویژگی‌های refresh ، append و prepend از CombinedLoadStates برای دسترسی به یک شیء LoadState برای نوع بارگذاری مناسب استفاده کنید. این ویژگی‌ها معمولاً در صورت وجود، به وضعیت بارگذاری از پیاده‌سازی RemoteMediator وابسته هستند؛ در غیر این صورت، حاوی وضعیت بارگذاری مناسب از پیاده‌سازی PagingSource هستند. برای اطلاعات بیشتر در مورد منطق اساسی، به مستندات مرجع CombinedLoadStates مراجعه کنید.

کاتلین

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
  }
}

جاوا

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;
});

جاوا

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;
});

با این حال، مهم است به یاد داشته باشید که فقط حالت‌های بارگذاری PagingSource تضمین می‌شوند که با به‌روزرسانی‌های UI همگام باشند. از آنجا که ویژگی‌های refresh ، append و prepend می‌توانند به طور بالقوه حالت بارگذاری را از PagingSource یا RemoteMediator بگیرند، تضمینی برای همگام بودن آنها با به‌روزرسانی‌های UI وجود ندارد. این می‌تواند باعث مشکلات UI شود که در آن بارگذاری قبل از اضافه شدن هر داده جدید به UI به پایان می‌رسد.

به همین دلیل، ابزارهای کمکی برای نمایش وضعیت بارگذاری در سربرگ یا پاورقی به خوبی کار می‌کنند، اما برای موارد دیگر ممکن است لازم باشد که به طور خاص از طریق PagingSource یا RemoteMediator به وضعیت بارگذاری دسترسی پیدا کنید. CombinedLoadStates ویژگی‌های source و mediator را برای این منظور فراهم می‌کند. هر یک از این ویژگی‌ها یک شیء LoadStates را در معرض نمایش قرار می‌دهند که به ترتیب شامل اشیاء LoadState برای PagingSource یا RemoteMediator است:

کاتلین

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
  }
}

جاوا

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;
});

جاوا

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

از آنجایی که شیء CombinedLoadStates به تمام تغییرات در وضعیت بارگذاری دسترسی می‌دهد، فیلتر کردن جریان وضعیت بارگذاری بر اساس رویدادهای خاص بسیار مهم است. این امر تضمین می‌کند که رابط کاربری خود را در زمان مناسب به‌روزرسانی کنید تا از وقفه‌ها و به‌روزرسانی‌های غیرضروری رابط کاربری جلوگیری شود.

برای مثال، فرض کنید می‌خواهید یک نمای خالی را نمایش دهید، اما فقط پس از اتمام بارگذاری اولیه داده‌ها. این مورد استفاده مستلزم آن است که تأیید کنید بارگذاری به‌روزرسانی داده‌ها آغاز شده است، سپس منتظر بمانید تا وضعیت NotLoading تأیید کند که به‌روزرسانی تکمیل شده است. شما باید تمام سیگنال‌ها را به جز آنهایی که نیاز دارید فیلتر کنید:

کاتلین

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) }
}

جاوا

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);
});

جاوا

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);
  }
});

این مثال تا زمانی که وضعیت بارگذاری refresh به‌روزرسانی شود، منتظر می‌ماند، اما فقط زمانی فعال می‌شود که وضعیت NotLoading باشد. این تضمین می‌کند که refresh از راه دور قبل از هرگونه به‌روزرسانی رابط کاربری به طور کامل پایان یافته است.

APIهای جریانی این نوع عملیات را ممکن می‌سازند. برنامه شما می‌تواند رویدادهای بارگذاری مورد نیاز خود را مشخص کند و داده‌های جدید را در صورت برآورده شدن معیارهای مناسب مدیریت کند.

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}