مدیریت و ارائه وضعیت‌های بارگیری

کتابخانه «صفحه‌بندی» وضعیت درخواست‌های بارگیری را برای داده‌های صفحه‌بندی‌شده ردیابی می‌کند و آن را ازطریق کلاس LoadState نشان می‌دهد.

سیگنال LoadState جداگانه‌ای برای هر LoadType و نوع منبع داده (PagingSource یا RemoteMediator) ارائه می‌شود. شیء CombinedLoadStates ارائه‌شده توسط شنونده اطلاعاتی درباره وضعیت بارگیری از همه این سیگنال‌ها ارائه می‌دهد. می‌توانید از این اطلاعات دقیق برای نمایش نشانگرهای بارگیری مناسب به کاربران استفاده کنید.

درحال بار کردن استان‌ها

کتابخانه «صفحه‌بندی» وضعیت بارگیری را برای استفاده در واسط کاربر ازطریق شیء LoadState نشان می‌دهد. LoadState اشیاء بسته به وضعیت بارگیری فعلی، یکی از سه شکل را به خود می‌گیرند:

  • اگر عملیات بارگیری فعالی وجود نداشته باشد و خطایی رخ نداده باشد، LoadState یک شیء LoadState.NotLoading است. این زیرکلاس همچنین شامل ویژگی endOfPaginationReached است که نشان می‌دهد آیا به پایان صفحه‌بندی رسیده‌ایم یا نه.
  • اگر عملیات بارگیری فعالی وجود داشته باشد، LoadState یک LoadState.Loading شیء است.
  • اگر خطایی وجود داشته باشد، LoadState یک شیء LoadState.Error است.

ازطریق ویژگی loadState در پوشاننده LazyPagingItems به این حالت‌ها دسترسی پیدا کنید. می‌توانید از این حالت به دو روش استفاده کنید: مدیریت نمایان بودن محتوای اصلی (مانند نشانگر چرخان بازآوری تمام‌صفحه) یا درج موارد درحال بارگیری مستقیماً در جریان LazyColumn (مانند نشانگر چرخان پانویس).

دسترسی به وضعیت بارگیری با شنونده

برای پایش وضعیت بارگیری در واسط کاربر، از ویژگی loadState ارائه‌شده توسط پوشاننده LazyPagingItems استفاده کنید. این کار شیء CombinedLoadStates را برمی‌گرداند که به شما امکان می‌دهد به رفتار بار کردن برای رویدادهای بازآوری، افزودن، یا پیش‌افزودن واکنش نشان دهید.

در مثال زیر، میانای کاربری بسته به وضعیت فعلی بارگیری بازآوری (اولیه)، گردونه بارگیری یا پیام خطا نمایش می‌دهد:

@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 پایش کنید.

در مثال زیر، وقتی داده‌های بیشتری واکشی می‌شود، فهرست یک نوار پیشرفت یا دکمه امتحان مجدد را در پایین فهرست نمایش می‌دهد:

@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) را پنهان می‌کند. این امر می‌تواند باعث شود که واسط کاربر برای مدت کوتاهی یک نشانگر بارگیری را نشان دهد، حتی زمانی که داده‌های ذخیره‌شده در حافظه نهان بلافاصله دردسترس هستند.

برای کنترل دقیق، مثل نشان دادن نشانگر بارگیری فقط وقتی که پایگاه داده محلی خالی است و همگام‌سازی شبکه فعال است، مستقیماً در composable خود به ویژگی‌های 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()
    }
}

چون pagingItems.loadState هربار که prepend یا append درطول پیمایش تغییر می‌کند، شیء CombinedLoadStates جدیدی منتشر می‌کند، بررسی بازآوری را در derivedStateOf با remember(pagingItems) بپیچید. این کار از بازترکیب کردن محدوده والد توسط به‌روزرسانی‌های لبه پیمایش که فقط نشانگر بازآوری تمام‌صفحه را تغییر وضعیت می‌دهد جلوگیری می‌کند:

val showFullScreenLoading by remember(pagingItems) {
  derivedStateOf {
    // Avoid recomposing on prepend or append updates while scrolling, and
    // avoid covering existing items during a background refresh.
    pagingItems.loadState.source.refresh is LoadState.Loading &&
      pagingItems.itemCount == 0
  }
}

if (showFullScreenLoading) {
  Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
  ) {
    CircularProgressIndicator()
  }
}

واکنش نشان دادن به تغییرات وضعیت بارگیری

ممکن است لازم باشد عوارض جانبی یک‌باره را براساس تغییرات وضعیت بارگیری راه‌اندازی کنید، مانند پیمایش به بالای فهرست یا نمایش Snackbar هنگام تکمیل یا عدم موفقیت بازآوری.

از snapshotFlow در LaunchedEffect استفاده کنید تا تغییرات وضعیت را به‌صورت جاری‌سازی مشاهده کنید. این به شما امکان می‌دهد تا عملگرهای استاندارد Flow مانند filter، distinctUntilChanged، و distinctUntilChangedBy را برای جداسازی رویدادهای خاص اعمال کنید.

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

به‌طور مشابه، می‌توانید از distinctUntilChangedBy در loadState برای نمایش خطای Snackbar فقط زمانی که refresh به LoadState.Error انتقال می‌یابد استفاده کنید:

val snackbarHostState = remember { SnackbarHostState() }

// Key on pagingItems rather than loadState so state updates do not restart the
// coroutine and reset distinctUntilChangedBy.
LaunchedEffect(pagingItems) {
  snapshotFlow { pagingItems.loadState }
    // Ignore prepend and append updates so scrolling does not re-trigger the
    // refresh error Snackbar.
    .distinctUntilChangedBy { it.refresh }
    .filter { it.refresh is LoadState.Error }
    .collect { loadState ->
      val error = (loadState.refresh as LoadState.Error).error
      val result = snackbarHostState.showSnackbar(
        message = "Data failed to load: ${error.localizedMessage ?: "Unknown error"}",
        actionLabel = "Retry"
      )
      if (result == SnackbarResult.ActionPerformed) {
        pagingItems.retry()
      }
    }
}

منابع بیشتر

برای اطلاعات بیشتر درباره کتابخانه «صفحه‌بندی» و وضعیت‌های بارگیری، به منابع زیر مراجعه کنید.

اسناد

محتوا را مشاهده می‌کند