تتتبّع مكتبة Paging حالة طلبات تحميل البيانات المقسّمة إلى صفحات وتعرضها من خلال الفئة LoadState.
يتم توفير إشارة LoadState منفصلة لكل
LoadType ونوع مصدر بيانات
(إما PagingSource أو
RemoteMediator). يوفّر عنصر
CombinedLoadStates
الذي يوفّره المستمع معلومات عن حالة التحميل
من جميع هذه الإشارات. يمكنك استخدام هذه المعلومات التفصيلية لعرض
مؤشرات التحميل المناسبة للمستخدمين.
جارٍ تحميل الولايات
تعرض مكتبة Paging حالة التحميل لاستخدامها في واجهة المستخدم من خلال الكائن LoadState. تتّخذ عناصر LoadState أحد الأشكال الثلاثة التالية حسب حالة التحميل الحالية:
- إذا لم تكن هناك عملية تحميل نشطة ولم يحدث أي خطأ، تكون السمة
LoadStateعبارة عن عنصرLoadState.NotLoading. تتضمّن هذه الفئة الفرعية أيضًا السمةendOfPaginationReachedالتي تشير إلى ما إذا تم الوصول إلى نهاية التقسيم على صفحات. - إذا كانت هناك عملية تحميل نشطة، تكون
LoadStateعنصرًا من النوعLoadState.Loading. - في حال حدوث خطأ، يكون
LoadStateكائنLoadState.Error.
يمكنك الوصول إلى هذه الحالات من خلال السمة loadState في برنامج تضمين LazyPagingItems. يمكنك استخدام هذه الحالة بطريقتَين: معالجة
مستوى ظهور المحتوى الرئيسي (مثل مؤشر تحميل بملء الشاشة) أو إدراج
عناصر التحميل مباشرةً في خلاصة LazyColumn (مثل مؤشر تحميل في التذييل).
الوصول إلى حالة التحميل باستخدام أداة معالجة الأحداث
لمراقبة حالة التحميل في واجهة المستخدم، استخدِم السمة loadState التي يوفّرها برنامج تضمين LazyPagingItems. يؤدي ذلك إلى عرض عنصر
CombinedLoadStates يتيح لك التفاعل مع سلوك التحميل لأحداث
إعادة التحميل أو الإلحاق أو الإضافة إلى البداية.
In the following example, the UI displays a loading spinner or an error message depending on the current state of the refresh (initial) load:
@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).
قد يؤدي ذلك إلى ظهور مؤشر تحميل لفترة وجيزة في واجهة المستخدم حتى عندما تكون البيانات المخزّنة مؤقتًا متاحة على الفور.
للتحكّم بدقة، مثل عرض مؤشر تحميل دوّار فقط عندما تكون قاعدة البيانات المحلية
فارغة وتكون مزامنة الشبكة نشطة، يمكنك الوصول إلى السمتَين 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 يُصدر كائن CombinedLoadStates جديدًا كلما
تغيّر prepend أو append أثناء الانتقال للأعلى أو للأسفل، يمكنك تضمين عملية التحقّق من إعادة التحميل في
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() } } }
مراجع إضافية
لمزيد من المعلومات حول مكتبة Paging وحالات التحميل، يُرجى الاطّلاع على المراجع التالية.
الوثائق
محتوى المشاهدات
التطبيقات المقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما يكون JavaScript غير مفعَّل
- نظرة عامة على مكتبة Paging