מושגים ויישום ב-Jetpack פיתוח נייטיב
ספריית Paging עוקבת אחרי הסטטוס של בקשות טעינה של נתונים עם חלוקה לדפים, ומציגה אותו באמצעות המחלקה LoadState.
האפליקציה יכולה לרשום מאזין ב-PagingDataAdapter כדי לקבל מידע על המצב הנוכחי ולעדכן את ממשק המשתמש בהתאם. המצבים האלה מסופקים מהמתאם כי הם סינכרוניים עם ממשק המשתמש.
המשמעות היא שהמאזין מקבל עדכונים כשטעינת הדף חלה על ממשק המשתמש.
אות LoadState נפרד מסופק לכל LoadType ולכל סוג של מקור נתונים (PagingSource או RemoteMediator). האובייקט CombinedLoadStates שמסופק על ידי ה-listener מספק מידע על מצב הטעינה מכל האותות האלה. אתם יכולים להשתמש במידע המפורט הזה כדי להציג למשתמשים את אינדיקטורי הטעינה המתאימים.
מצבי טעינה
ספריית Paging חושפת את מצב הטעינה לשימוש בממשק המשתמש דרך האובייקט LoadState. אובייקטים מסוג LoadState יכולים להופיע באחת משלוש צורות, בהתאם למצב הטעינה הנוכחי:
- אם אין פעולת טעינה פעילה ואין שגיאה, אז
LoadStateהוא אובייקטLoadState.NotLoading. מחלקת המשנה הזו כוללת גם את המאפייןendOfPaginationReached, שמציין אם הגעתם לסוף של חלוקת הדפים. - אם יש פעולת טעינה פעילה, אז
LoadStateהוא אובייקטLoadState.Loading. - אם יש שגיאה, אז
LoadStateהוא אובייקטLoadState.Error.
יש שתי דרכים להשתמש ב-LoadState בממשק המשתמש: באמצעות listener או באמצעות מתאם רשימה מיוחד להצגת מצב הטעינה ישירות ברשימה RecyclerView.
גישה למצב הטעינה באמצעות מאזין
כדי לקבל את מצב הטעינה לשימוש כללי בממשק המשתמש, משתמשים בשיטה loadStateFlow או בשיטה addLoadStateListener() שסופקה על ידי PagingDataAdapter. המנגנונים האלה מספקים גישה לאובייקט CombinedLoadStates שכולל מידע על ההתנהגות של LoadState לכל סוג טעינה.
בדוגמה הבאה, הרכיב PagingDataAdapter מציג רכיבי ממשק משתמש שונים בהתאם למצב הנוכחי של טעינת הרענון:
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 זמין במאמר גישה למידע נוסף על מצב הטעינה.
הצגת סטטוס הטעינה באמצעות מתאם
ספריית Paging מספקת מתאם רשימה נוסף בשם 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(). השיטות האלה יוצרות מופע של placeholder לתצוגה בהתאמה אישית ומקשרות את מצב הטעינה המשויך.
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); } }
הצגת סטטוס הטעינה ככותרת עליונה או תחתונה
כדי להציג את התקדמות הטעינה בכותרת ובכותרת התחתונה, קוראים ל-method 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מצבי הטעינה
מסונכרנים בוודאות עם עדכוני ממשק המשתמש. יכול להיות שהמאפיינים refresh,
append ו-prepend יקבלו את מצב הטעינה מ-PagingSource או מ-RemoteMediator, ולכן לא מובטח שהם יהיו מסונכרנים עם עדכוני ממשק המשתמש. הדבר עלול לגרום לבעיות בממשק המשתמש, שבהן נראה שהטעינה מסתיימת לפני שנוספו לממשק המשתמש נתונים חדשים.
לכן, פונקציות הגישה הנוחות מתאימות להצגת מצב הטעינה בכותרת עליונה או בכותרת תחתונה, אבל בתרחישי שימוש אחרים יכול להיות שתצטרכו לגשת למצב הטעינה באופן ספציפי מ-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 מספק גישה לכל השינויים במצב הטעינה, ולכן חשוב לסנן את הזרם של מצב הטעינה על סמך אירועים ספציפיים. כך תוכלו לוודא שאתם מעדכנים את ממשק המשתמש בזמן המתאים כדי למנוע גמגום ועדכונים מיותרים של ממשק המשתמש.
לדוגמה, נניח שרוצים להציג תצוגה ריקה, אבל רק אחרי שהטעינה הראשונית של הנתונים מסתיימת. במקרה השימוש הזה, צריך לוודא שהתחיל טעינה של רענון נתונים, ואז להמתין למצב 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. כך מוודאים שהרענון מרחוק הסתיים באופן מלא לפני שמתבצעים עדכונים בממשק המשתמש.
ממשקי API של סטרימינג מאפשרים לבצע פעולות כאלה. האפליקציה יכולה לציין את אירועי הטעינה שהיא צריכה ולטפל בנתונים החדשים כשמתקיימים הקריטריונים המתאימים.
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- טעינה והצגה של נתונים עם חלוקה לדפים
- דף מהרשת וממסד הנתונים
- סקירה כללית של ספריית Paging