ספריית Paging מספקת יכולות מתקדמות לטעינה ולהצגה של נתונים עם חלוקה לדפים מתוך מערך נתונים גדול יותר. במדריך הזה נדגים איך להשתמש בספריית Paging כדי להגדיר סטרימינג של נתונים עם חלוקה לדפים ממקור נתונים ברשת ולהציג אותם ברשימה עצלה.
הגדרת מקור נתונים
השלב הראשון הוא להגדיר הטמעה של PagingSource כדי לזהות את מקור הנתונים. המחלקה PagingSource API כוללת את ה-method load, שמוחלפת כדי לציין איך לאחזר נתונים עם חלוקה לעמודים ממקור הנתונים המתאים.
אפשר להשתמש ישירות במחלקה PagingSource כדי להשתמש ב-coroutines של Kotlin לטעינה אסינכרונית.
בחירת סוגי המפתח והערך
ל-PagingSource<Key, Value> יש שני פרמטרים של סוג: Key ו-Value. המפתח
מגדיר את המזהה שמשמש לטעינת הנתונים, והערך הוא סוג הנתונים עצמם. לדוגמה, אם טוענים דפים של User אובייקטים מהרשת על ידי העברת מספרי הדפים Int אל Retrofit, בוחרים באפשרות Int כסוג Key ובאפשרות User כסוג Value.
הגדרת ה-PagingSource
בדוגמה הבאה מוטמע PagingSource שטוען דפים של פריטים לפי מספר הדף. הסוג של Key הוא Int והסוג של Value הוא User.
class ExamplePagingSource(
val backend: ExampleBackendService,
val query: String
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
init {
// the data source is expected to be immutable
// invalidate PagingSource if data source
// has updated
backEnd.addDatabaseOnChangedListener {
invalidate()
}
}
try {
// Start refresh at page 1 if undefined.
val nextPageNumber = params.key ?: 1
val response = backend.searchUsers(query, nextPageNumber)
return LoadResult.Page(
data = response.users,
prevKey = null, // Only paging forward.
nextKey = nextPageNumber + 1
)
} catch (e: Exception) {
// Handle errors in this block and return LoadResult.Error for
// expected errors (such as a network failure).
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Try to find the page key of the closest page to anchorPosition from
// either the prevKey or the nextKey; you need to handle nullability
// here.
// * prevKey == null -> anchorPage is the first page.
// * nextKey == null -> anchorPage is the last page.
// * both prevKey and nextKey are null -> anchorPage is the
// initial page, so return null.
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
בדרך כלל, הטמעה של PagingSource מעבירה פרמטרים שמוגדרים ב-constructor שלה לשיטה load כדי לטעון נתונים מתאימים לשאילתה. בדוגמה שלמעלה, הפרמטרים האלה הם:
-
backend: מופע של שירות הקצה העורפי שמספק את הנתונים -
query: שאילתת החיפוש שרוצים לשלוח לשירות שמצוין על ידיbackend
האובייקט LoadParams
מכיל מידע על פעולת הטעינה שצריך לבצע. הוא כולל את המפתח לטעינה ואת מספר הפריטים לטעינה.
האובייקט LoadResult
מכיל את התוצאה של פעולת הטעינה. LoadResult הוא מחלקה אטומה
שמקבלת אחת משלוש צורות, בהתאם להצלחה של הקריאה ל-load:
- אם הטעינה מצליחה, מחזירים אובייקט
LoadResult.Page. - אם הטעינה לא מצליחה, מחזירים אובייקט
LoadResult.Error. - אם
PagingSourceכבר לא תקף וצריך להחליף אותו במופע חדש (לדוגמה, בגלל שינוי בנתונים הבסיסיים), מחזירים אובייקטLoadResult.Invalid.
באיור הבא אפשר לראות איך הפונקציה load בדוגמה הזו מקבלת את המפתח לכל טעינה ומספקת את המפתח לטעינה הבאה.
load משתמש במפתח ומעדכן אותו.
בנוסף, ההטמעה של PagingSource צריכה לכלול שיטה getRefreshKey שמקבלת אובייקט PagingState כפרמטר. היא מחזירה את המפתח להעברה לשיטה load כשהנתונים מתעדכנים או נפסלים אחרי הטעינה הראשונית. ספריית Paging קוראת לשיטה הזו באופן אוטומטי ברענונים הבאים של הנתונים.
טיפול בשגיאות
בקשות לטעינת נתונים עלולות להיכשל מכמה סיבות, במיוחד כשמבצעים טעינה דרך רשת. כדי לדווח על שגיאות שמתרחשות במהלך הטעינה, מחזירים אובייקט LoadResult.Error מהשיטה load.
לדוגמה, אפשר לזהות ולדווח על שגיאות טעינה ב-ExamplePagingSource
מהדוגמה הקודמת על ידי הוספת הקוד הבא לשיטה load:
catch (e: IOException) {
// IOException for network failures.
return LoadResult.Error(e)
} catch (e: HttpException) {
// HttpException for any non-2xx HTTP status codes.
return LoadResult.Error(e)
}
מידע נוסף על טיפול בשגיאות ב-Retrofit זמין בדוגמאות בPagingSourceהפניית API.
PagingSource אוסף ומעביר אובייקטים של LoadResult.Error לממשק המשתמש כדי שתוכלו לפעול לפיהם. מידע נוסף על הצגת מצב הטעינה בממשק המשתמש זמין במאמר ניהול והצגה של מצבי טעינה.
הגדרה של שידור נתונים של בקשות דפדוף
לאחר מכן, צריך להגדיר זרם של נתונים עם חלוקה לדפים מההטמעה של PagingSource.
מגדירים את מקור הנתונים ב-ViewModel. המחלקה Pager מספקת שיטות שחושפות זרם ריאקטיבי של אובייקטים מסוג PagingData מ-PagingSource. ספריית Paging חושפת את זרם הנתונים כ-Flow.
כשיוצרים מופע Pager כדי להגדיר את הזרם הריאקטיבי, צריך לספק למופע אובייקט הגדרה PagingConfig ופונקציה שמסבירה ל-Pager איך לקבל מופע של הטמעת PagingSource, כמו בדוגמה הבאה.
class UserViewModel(
private val backend: ExampleBackendService,
private val query: String
) : ViewModel() {
val userPagingFlow: Flow<PagingData<User>> = Pager(
// Configure how data is loaded by passing additional properties to
// PagingConfig, such as pageSize and enabling or disabling placeholders.
config = PagingConfig(
pageSize = 20,
enablePlaceholders = true
),
pagingSourceFactory = {
ExamplePagingSource(backend, query)
}
)
.flow
.cachedIn(viewModelScope)
}
האופרטור cachedIn מאפשר לשתף את מקור הנתונים ומאחסן במטמון את הנתונים שנטענו עם CoroutineScope שסופק. בלי cachedIn, אי אפשר לזכור את PagingData. בדוגמה הזו נעשה שימוש ב-viewModelScope שסופק על ידי ארטיפקט מחזור החיים lifecycle-viewmodel-ktx.
האובייקט Pager קורא לשיטה load מהאובייקט PagingSource, ומספק לו את האובייקט LoadParams ומקבל בתמורה את האובייקט LoadResult.
איסוף הנתונים והצגתם בממשק המשתמש
כדי לחבר את הזרם עם החלוקה לדפים לממשק המשתמש, צריך לקבל את ה-Flow מ-ViewModel ולהעביר אותו לרכיב ה-Composable של הרשימה.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
אפשר להשתמש ב-collectAsLazyPagingItems כדי להמיר את התהליך PagingData ל-LazyPagingItems. לאחר מכן, משתמשים ב-items API בתוך LazyColumn כדי להגדיר את הפריסה של כל פריט.
חשוב לספק מזהה ייחודי וקבוע לכל פריט באמצעות מאפיין המזהה itemKey.
בדוגמה הבאה נעשה שימוש ב-it.id (בהפניה לנכס User.id) כי הוא נשאר יציב עבור מופע User בכל העדכונים של הנתונים.
@Composable
fun UserList(flow: Flow<PagingData<User>>) {
val lazyPagingItems = flow.collectAsLazyPagingItems()
LazyColumn {
items(
lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val user = lazyPagingItems[index]
if (user != null) {
UserRow(user)
} else {
UserPlaceholder()
}
}
}
}
ספריית Paging משתמשת ב-null בשביל פלייסהולדרים בזמן טעינת דף. לכן, אם הפעלתם פלייסהולדרים, אתם צריכים לטפל בערכים של null בבלוק התוכן.
עכשיו הרשימה מציגה את הנתונים שחולקו לדפים, וספריית Paging טוענת דפים נוספים כשהמשתמש גולל.
מקורות מידע נוספים
איפה אפשר למצוא מידע נוסף על ספריית Paging?
תיעוד
צפייה בתוכן
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- דף מהרשת וממסד הנתונים
- העברה ל-Paging 3
- סקירה כללית של ספריית Paging