จัดการและนำเสนอสถานะการโหลด

ไลบรารีการแบ่งหน้าจะติดตามสถานะของคำขอโหลดสำหรับข้อมูลที่แบ่งหน้าและเปิดเผย ผ่านชั้นเรียน LoadState แอปของคุณสามารถลงทะเบียน Listener ด้วย PagingDataAdapter ถึง รับข้อมูลเกี่ยวกับสถานะปัจจุบันของ และอัปเดต UI ให้สอดคล้องกัน เหล่านี้ สถานะจะแสดงจากอะแดปเตอร์เนื่องจากซิงโครนัสกับ UI ซึ่งหมายความว่า Listener ของคุณจะได้รับการอัปเดตเมื่อการโหลดหน้าเว็บ ที่ใช้กับ UI แล้ว

สัญญาณ LoadState แต่ละรายการมีไว้สำหรับแต่ละสัญญาณ LoadType และประเภทแหล่งข้อมูล (PagingSource หรือ RemoteMediator) CombinedLoadStates ออบเจ็กต์ที่ Listener ระบุไว้ให้ข้อมูลเกี่ยวกับสถานะการโหลด จากสัญญาณทั้งหมดเหล่านี้ คุณสามารถใช้ข้อมูลโดยละเอียดนี้เพื่อแสดง สัญญาณบอกสถานะการโหลดที่เหมาะสมให้กับผู้ใช้

กำลังโหลดสถานะ

ไลบรารีการแบ่งหน้าจะแสดงสถานะการโหลดเพื่อใช้ใน UI ผ่านทาง ออบเจ็กต์ LoadState รายการ ออบเจ็กต์ LoadState รายการมี 1 ใน 3 รูปแบบขึ้นอยู่กับ สถานะการโหลดปัจจุบัน:

  • หากไม่มีการดำเนินการโหลดที่ทำงานอยู่และไม่มีข้อผิดพลาด LoadState จะเป็น LoadState.NotLoading ออบเจ็กต์ คลาสย่อยนี้ยังรวมถึง endOfPaginationReached ซึ่งระบุว่าถึงส่วนท้ายของหน้าแล้วหรือไม่
  • หากมีการโหลดที่ใช้งานอยู่ LoadState จะเป็น LoadState.Loading ออบเจ็กต์
  • หากมีข้อผิดพลาด LoadState จะเป็น LoadState.Error

การใช้ LoadState ใน UI มี 2 วิธี ได้แก่ การใช้ Listener หรือการใช้ อะแดปเตอร์รายการพิเศษเพื่อแสดงสถานะการโหลดโดยตรงใน RecyclerView รายการ

เข้าถึงสถานะการโหลดด้วย Listener

หากต้องการดูสถานะการโหลดสำหรับการใช้งานทั่วไปใน UI ให้ใช้ loadStateFlow หรือ addLoadStateListener() ที่ได้รับจาก PagingDataAdapter กลไกเหล่านี้ช่วยให้เข้าถึง ออบเจ็กต์ CombinedLoadStates ที่มีข้อมูลเกี่ยวกับ LoadState สำหรับการโหลดแต่ละประเภท

ในตัวอย่างต่อไปนี้ PagingDataAdapter แสดง UI ที่แตกต่างกัน ที่ขึ้นอยู่กับสถานะปัจจุบันของการโหลดการรีเฟรช

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 ได้ที่เข้าถึงการโหลดเพิ่มเติม ข้อมูลของรัฐ

แสดงสถานะการโหลดด้วยอะแดปเตอร์

ไลบรารีการสร้างหน้าจะมีอะแดปเตอร์รายการอีกรายการหนึ่งที่ชื่อ LoadStateAdapter สำหรับ วัตถุประสงค์ของการแสดงสถานะการโหลดโดยตรงในรายการที่แสดงแบบแบ่งหน้า อะแดปเตอร์นี้ให้การเข้าถึงสถานะการโหลดปัจจุบันของรายการ ซึ่ง เพื่อส่งผ่านไปยังมุมมองที่กำหนดเองที่แสดงข้อมูลนั้น

ก่อนอื่น ให้สร้างคลาสผู้ถือครองข้อมูลพร็อพเพอร์ตี้ที่เก็บการอ้างอิงถึงการโหลดและข้อผิดพลาด บนหน้าจอ สร้างฟังก์ชัน bind() ที่ยอมรับ LoadState เป็น พารามิเตอร์ ฟังก์ชันนี้ควรสลับการแสดงมุมมองตามการโหลด พารามิเตอร์สถานะ:

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

จากนั้นให้สร้างคลาสที่ใช้งาน LoadStateAdapter และกำหนด onCreateViewHolder() และ onBindViewHolder() วิธีการเหล่านี้จะสร้างอินสแตนซ์ของตัวยึดตำแหน่งมุมมองที่กำหนดเองและเชื่อมโยง สถานะการโหลดที่เกี่ยวข้อง

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

หากต้องการแสดงความคืบหน้าในการโหลดในส่วนหัวและส่วนท้าย ให้เรียกเมธอด withLoadStateHeaderAndFooter() จากออบเจ็กต์ PagingDataAdapter ของคุณ:

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

คุณสามารถโทรหา withLoadStateHeader() หรือ withLoadStateFooter() หากคุณต้องการให้รายการ RecyclerView แสดงสถานะการโหลดเฉพาะใน หรือเฉพาะในส่วนท้าย

เข้าถึงข้อมูลสถานะการโหลดเพิ่มเติม

ออบเจ็กต์ CombinedLoadStates จาก PagingDataAdapter ให้ข้อมูลเกี่ยวกับ สถานะการโหลดสำหรับการใช้งาน PagingSource ของคุณ และยังสำหรับ การติดตั้งใช้งาน RemoteMediator หากมี

เพื่อความสะดวก คุณสามารถใช้ refresh append และ prepend พร็อพเพอร์ตี้จาก CombinedLoadStates เพื่อเข้าถึงออบเจ็กต์ LoadState สำหรับ ประเภทการโหลดที่เหมาะสม โดยทั่วไปแล้ว พร็อพเพอร์ตี้เหล่านี้จะยึดตามสถานะการโหลดจาก การใช้งาน RemoteMediator หากมี มิฉะนั้นจะมี สถานะการโหลดที่เหมาะสมจากการใช้งาน PagingSource หากต้องการทราบรายละเอียดเพิ่มเติม เกี่ยวกับตรรกะที่สำคัญ โปรดดูเอกสารอ้างอิงสำหรับ CombinedLoadStates

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

อย่างไรก็ตาม โปรดทราบว่าเฉพาะสถานะการโหลด PagingSource เท่านั้น เพื่อให้แน่ใจว่าการอัปเดต UI ทำงานพร้อมกัน เนื่องจาก refresh พร็อพเพอร์ตี้ append และ prepend อาจรับสถานะการโหลดจาก PagingSource หรือ RemoteMediator ก็ไม่รับประกันว่าจะเป็น พร้อมกันพร้อมการอัปเดต UI ซึ่งอาจทำให้เกิดปัญหากับ UI ในตำแหน่งที่การโหลดปรากฏขึ้น ให้เสร็จก่อนที่จะเพิ่มข้อมูลใหม่ลงใน UI

ด้วยเหตุนี้ ตัวเข้าถึงเพื่ออำนวยความสะดวกจึงทำงานได้ดีในการแสดงโหลด ในส่วนหัวหรือส่วนท้าย แต่สำหรับกรณีการใช้งานอื่นๆ คุณอาจต้อง เข้าถึงสถานะการโหลดโดยเฉพาะจาก PagingSource หรือ RemoteMediator CombinedLoadStates จะมี source และ mediator เพื่อวัตถุประสงค์นี้ พร็อพเพอร์ตี้เหล่านี้แต่ละรายการจะแสดง LoadStates ที่ มีออบเจ็กต์ LoadState สำหรับ PagingSource หรือ RemoteMediator ตามลำดับ

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

เนื่องจากออบเจ็กต์ CombinedLoadStates ให้สิทธิ์เข้าถึงการเปลี่ยนแปลงทั้งหมดใน สถานะการโหลด สิ่งสำคัญคือต้องกรองสตรีมสถานะการโหลด โดยอิงตาม กิจกรรม วิธีนี้ช่วยให้มั่นใจว่าคุณอัปเดต UI ในเวลาที่เหมาะสมเพื่อหลีกเลี่ยง การกระตุกและการอัปเดต UI ที่ไม่จำเป็น

ตัวอย่างเช่น สมมติว่าคุณต้องการแสดงมุมมองว่างเปล่า แต่หลังจาก การโหลดข้อมูลเริ่มต้นเสร็จสมบูรณ์ กรณีการใช้งานนี้กำหนดให้คุณต้องยืนยันว่า เริ่มโหลดการรีเฟรชแล้ว จากนั้นรอให้สถานะ NotLoading ยืนยันว่า การรีเฟรชเสร็จสมบูรณ์แล้ว คุณต้องกรองสัญญาณทั้งหมดออก ยกเว้นสัญญาณ สิ่งที่ต้องมี

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

ตัวอย่างนี้รอจนกว่าจะอัปเดตสถานะโหลดการรีเฟรช แต่จะมีเฉพาะทริกเกอร์เท่านั้น เมื่อรัฐคือ NotLoading การดำเนินการนี้จะช่วยให้มั่นใจว่าการรีเฟรชจากระยะไกลได้ เสร็จก่อนการอัปเดต UI

API ของสตรีมทำให้การดำเนินการประเภทนี้เป็นไปได้ แอปของคุณระบุโหลดได้ เหตุการณ์ที่ต้องการและจัดการข้อมูลใหม่เมื่อตรงกับเกณฑ์ที่เหมาะสม