แนวคิดและการใช้งาน Jetpack Compose
ไลบรารีการแบ่งหน้าจะติดตามสถานะของคำขอโหลดสำหรับข้อมูลที่แบ่งหน้าและแสดงสถานะผ่านคลาส LoadState
แอปของคุณสามารถลงทะเบียน Listener ด้วย
PagingDataAdapter เพื่อ
รับข้อมูลเกี่ยวกับสถานะปัจจุบันและอัปเดต UI ตามนั้น สถานะเหล่านี้มาจากอแดปเตอร์เนื่องจากเป็นแบบซิงโครนัสกับ UI
ซึ่งหมายความว่า Listener จะได้รับการอัปเดตเมื่อมีการใช้การโหลดหน้าเว็บกับ UI
ระบบจะให้LoadStateสัญญาณแยกต่างหากสำหรับแต่ละLoadTypeและประเภทแหล่งข้อมูล
(ไม่ว่าจะเป็น PagingSource หรือ
RemoteMediator) ออบเจ็กต์ CombinedLoadStates
ที่ Listener ระบุจะให้ข้อมูลเกี่ยวกับสถานะการโหลด
จากสัญญาณทั้งหมดเหล่านี้ คุณสามารถใช้ข้อมูลโดยละเอียดนี้เพื่อแสดงตัวบ่งชี้การโหลดที่เหมาะสมต่อผู้ใช้
สถานะการโหลด
ไลบรารีการแบ่งหน้าจะแสดงสถานะการโหลดเพื่อใช้ใน UI ผ่านออบเจ็กต์
LoadState LoadState จะมีรูปแบบใดรูปแบบหนึ่งใน 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; });
โอเปอเรเตอร์ Chain ใน 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 สตรีมช่วยให้การดำเนินการประเภทนี้เป็นไปได้ แอปของคุณระบุเหตุการณ์ load ที่ต้องการและจัดการข้อมูลใหม่ได้เมื่อตรงตามเกณฑ์ที่เหมาะสม
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- โหลดและแสดงข้อมูลแบบแบ่งหน้า
- หน้าจากเครือข่ายและฐานข้อมูล
- ภาพรวมไลบรารีการแบ่งหน้า