ניהול והצגה של מצבי הטעינה

ספריית Paging עוקבת אחרי הסטטוס של בקשות טעינה של נתונים עם חלוקה לדפים, ומציגה אותו באמצעות המחלקה LoadState.

אות LoadState נפרד מסופק לכל LoadType ולכל סוג של מקור נתונים (PagingSource או RemoteMediator). האובייקט CombinedLoadStates שמסופק על ידי ה-listener מספק מידע על מצב הטעינה מכל האותות האלה. אתם יכולים להשתמש במידע המפורט הזה כדי להציג למשתמשים את אינדיקטורי הטעינה המתאימים.

מצבי טעינה

ספריית Paging חושפת את מצב הטעינה לשימוש בממשק המשתמש באמצעות האובייקט LoadState. אובייקטים מסוג LoadState יכולים להופיע באחת משלוש צורות, בהתאם למצב הטעינה הנוכחי:

  • אם אין פעולת טעינה פעילה ואין שגיאה, אז LoadState הוא אובייקט LoadState.NotLoading. מחלקת המשנה הזו כוללת גם את המאפיין endOfPaginationReached, שמציין אם הגעתם לסוף של חלוקת הדפים.
  • אם יש פעולת טעינה פעילה, אז LoadState הוא אובייקט LoadState.Loading.
  • אם יש שגיאה, אז LoadState הוא אובייקט LoadState.Error.

אפשר לגשת למדינות האלה דרך המאפיין loadState של רכיב העטיפה LazyPagingItems. אפשר להשתמש במצב הזה בשתי דרכים: לטפל בנראות של התוכן הראשי (למשל, סמל טעינה של רענון במסך מלא) או להוסיף פריטי טעינה ישירות לזרם LazyColumn (למשל, סמל טעינה בכותרת התחתונה).

גישה למצב הטעינה באמצעות מאזין

כדי לעקוב אחר מצב הטעינה בממשק המשתמש, משתמשים בנכס loadState שמוגדר על ידי wrapper‏ LazyPagingItems. הפונקציה מחזירה אובייקט CombinedLoadStates שמאפשר להגיב להתנהגות הטעינה של אירועים מסוג refresh,‏ append או prepend.

בדוגמה הבאה, בממשק המשתמש מוצג סימן גרפי שפעולה מתבצעת או הודעת שגיאה, בהתאם למצב הנוכחי של הטעינה (ההתחלתית):

@Composable
fun UserListScreen(viewModel: UserViewModel) {
  val pagingItems = viewModel.flow.collectAsLazyPagingItems()

  Box(modifier = Modifier.fillMaxSize()) {
    // Show the list content
    LazyColumn {
      items(pagingItems.itemCount) { index ->
        UserItem(pagingItems[index])
      }
    }

    // Handle the loading state
    when (val state = pagingItems.loadState.refresh) {
      is LoadState.Loading -> {
        CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
      }
      is LoadState.Error -> {
        ErrorButton(
          message = state.error.message ?: "Unknown error",
          onClick = { pagingItems.retry() },
          modifier = Modifier.align(Alignment.Center)
        )
      }
      else -> {} // No separate view needed for success/not loading
    }
  }
}

מידע נוסף על LazyPagingItems זמין במאמר קבוצות גדולות של נתונים (חלוקה לדפים).

כדי להציג אינדיקטורים של טעינה בתחילת הרשימה או בסופה (ככותרות עליונות או כותרות תחתונות), מוסיפים בלוקים ייעודיים של פריטים במיוחד למצבים האלה בהיקף של LazyColumn.

אפשר לעקוב אחרי מצב ההוספה של הכותרת ומצב הצירוף של הכותרת התחתונה באמצעות האובייקט CombinedLoadStates.

בדוגמה הבאה, כשמאחזרים עוד נתונים, מוצג פס התקדמות או לחצן ניסיון חוזר בתחתית הרשימה:

פריטי מצב הטעינה יוצגו בסוף רכיבי ה-placeholder.
@Composable
fun UserList(viewModel: UserViewModel) {
  val pagingItems = viewModel.pager.flow.collectAsLazyPagingItems()

  LazyColumn {
    // 1. Header (Prepend state)
    // Useful if you support bidirectional paging or jumping to the middle
    item {
      val prependState = pagingItems.loadState.prepend
      if (prependState is LoadState.Loading) {
        LoadingItem()
      } else if (prependState is LoadState.Error) {
        ErrorItem(
          message = prependState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }

    // 2. Main Data
    items(pagingItems.itemCount) { index ->
      UserItem(pagingItems[index])
    }

    // 3. Footer (Append state)
    // Shows when the user scrolls to the bottom and more data is loading
    item {
      val appendState = pagingItems.loadState.append
      if (appendState is LoadState.Loading) {
        LoadingItem()
      } else if (appendState is LoadState.Error) {
        ErrorItem(
          message = appendState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }
  }
}

@Composable
fun LoadingItem() {
  Box(modifier = Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center) {
    CircularProgressIndicator()
  }
}

@Composable
fun ErrorItem(message: String, onClick: () -> Unit) {
  Column(
    modifier = Modifier.fillMaxWidth().padding(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Text(text = message, color = Color.Red)
    Button(onClick = onClick) { Text("Retry") }
  }
}

גישה למידע נוסף על מצב הטעינה

כפי שרואים בדוגמאות הקודמות, קל להתקשר אל pagingItems.loadState.refresh. עם זאת, הוא מסתיר את ההבדל בין טעינה ממסד הנתונים המקומי (PagingSource) לבין טעינה מהרשת (RemoteMediator). הדבר עלול לגרום להצגת סמל טעינה בממשק המשתמש למשך זמן קצר, גם כשנתונים במטמון זמינים באופן מיידי.

כדי לשלוט בצורה מדויקת, למשל להציג אנימציה של טעינה רק כשהמסד הנתונים המקומי ריק ומתבצע סנכרון עם הרשת, אפשר לגשת ישירות למאפיינים source ו-mediator בתוך הקומפוזיציה.

val loadState = pagingItems.loadState

val isSyncing = loadState.mediator?.refresh is LoadState.Loading

val isLocalEmpty = loadState.source.refresh is LoadState.NotLoading &&
                   pagingItems.itemSnapshotList.items.isEmpty()

if (isSyncing && isLocalEmpty) {
    FullScreenLoading()
} else {
    UserList(pagingItems)

    if (isSyncing) {
        TopOverlaySpinner()
    }
}

תגובה לשינויים במצב הטעינה

יכול להיות שתצטרכו להפעיל תופעות לוואי חד-פעמיות על סמך שינויים במצב הטעינה, כמו גלילה לראש הרשימה או הצגת Snackbar כשטעינה מחדש מסתיימת.

אפשר להשתמש ב-snapshotFlow בתוך LaunchedEffect כדי לראות את השינויים במצב כזרם. כך אפשר להחיל אופרטורים סטנדרטיים של Flow כמו filter ו-distinctUntilChanged כדי לבודד אירועים ספציפיים.

val listState = rememberLazyListState()

LaunchedEffect(pagingItems) {
  // 1. Convert the state to a Flow
  snapshotFlow { pagingItems.loadState.refresh }
    // 2. Filter for the specific event (Refresh completed successfully)
    .distinctUntilChanged()
    .filter { it is LoadState.NotLoading }
    .collect {
      // 3. Trigger the side effect
      listState.animateScrollToItem(0)
    }
}

מקורות מידע נוספים

מידע נוסף על ספריית Paging ומצבי טעינה זמין במקורות המידע הבאים.

תיעוד

צפייה בתוכן