توفّر مكتبة Paging إمكانات قوية لتحميل البيانات المقسّمة إلى صفحات وعرضها من مجموعة بيانات أكبر. يوضّح هذا الدليل كيفية استخدام مكتبة Paging لإعداد بث لبيانات مقسّمة إلى صفحات من مصدر بيانات على الشبكة وعرضها في قائمة كسولة.
تحديد مصدر بيانات
تتمثل الخطوة الأولى في تحديد تنفيذ PagingSource لتحديد مصدر البيانات. يتضمّن فئة واجهة برمجة التطبيقات PagingSource طريقة load،
التي يمكنك إلغاء تعريفها لتحديد كيفية استرداد البيانات المقسّمة إلى صفحات من مصدر البيانات
المناسب.
استخدِم الفئة PagingSource مباشرةً لاستخدام أنماط "كوروتين" في 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 من خلال تمرير المَعلمات المقدَّمة في أداة الإنشاء إلى طريقة 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 مرجع واجهة برمجة التطبيقات.
تجمع PagingSource عناصر LoadResult.Error وتقدّمها إلى واجهة المستخدم حتى تتمكّن من اتّخاذ إجراءات بشأنها. لمزيد من المعلومات حول عرض حالة التحميل في واجهة المستخدم، يُرجى الاطّلاع على إدارة حالات التحميل وعرضها.
إعداد بث PagingData
بعد ذلك، تحتاج إلى مصدر بيانات مقسَّمة إلى صفحات من عملية تنفيذ 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 في المقابل.
جمع البيانات وعرضها في واجهة المستخدم
لربط البث المقسّم إلى صفحات بواجهة المستخدم، احصل على التدفق من ViewModel ومرِّره إلى العنصر القابل للإنشاء الخاص بالقائمة.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
استخدِم collectAsLazyPagingItems لتحويل مسار PagingData إلى LazyPagingItems. بعد ذلك، استخدِم واجهة برمجة التطبيقات items ضمن 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