بار کردن و نمایش دادن داده‌های صفحه‌بندی‌شده

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

تعریف منبع داده

اولین گام تعریف پیاده‌سازی 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 حاوی اطلاعاتی درباره عملیات بارگیری است که باید انجام شود. این شامل کلیدی است که باید بار شود و تعداد مواردی که باید بار شود. برای تمایز بین انواع بارگیری در load، بررسی کنید کدام زیرکلاس LoadParams ارسال شده است: LoadParams.Refresh، LoadParams.Append، یا LoadParams.Prepend.

شیء LoadResult حاوی نتیجه عملیات بار کردن است. ‫LoadResult یک کلاس مهروموم‌شده است که بسته به اینکه فراخوانی load موفقیت‌آمیز بوده است یا نه، یکی از سه شکل را می‌گیرد:

  • اگر بارگیری موفقیت‌آمیز بود، شیء LoadResult.Page را برگردانید. اگر داده‌های بیشتری در آن جهت بار نشود، null را برای nextKey یا prevKey رد کنید.
  • اگر بارگیری موفقیت‌آمیز نبود، شیء LoadResult.Error را برگردانید.
  • اگر PagingSource به‌دلیل تغییر داده‌های زیرین دیگر معتبر نیست، شیء LoadResult.Invalid را برگردانید.

شکل زیر نشان می‌دهد که چگونه تابع load در این مثال کلید هر بارگیری را دریافت می‌کند و کلید بارگیری بعدی را ارائه می‌دهد.

در هر فراخوانی بارگیری، ExamplePagingSource کلید فعلی را می‌گیرد
    و کلید بعدی را برای بارگیری برمی‌گرداند.
شکل ۱. نموداری که نشان می‌دهد load چگونه از کلید استفاده می‌کند و آن را به‌روز می‌کند.

پیاده‌سازی PagingSource باید روش getRefreshKey را نیز پیاده‌سازی کند که شیء PagingState را به‌عنوان پارامتر می‌گیرد. این کلید را برمی‌گرداند تا وقتی داده‌ها پس‌از بارگیری اولیه بازآوری یا نامعتبر می‌شوند، به روش load منتقل شود. کتابخانه «صفحه‌بندی» این روش را به‌طور خودکار در بازآوری‌های بعدی داده‌ها فراخوانی می‌کند. برای صفحه‌بندی مبتنی بر آفست که در آن کلیدها نشان‌دهنده موقعیت‌های مورد هستند، می‌توانید کلید بازآوری را نسبت‌به state.anchorPosition محاسبه کنید:

// Only for positional or offset PagingSources where Key is a 0-based item index
// rather than a page number.
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
  // Center the refreshed load around the user's current viewport so the list
  // does not jump back to the top on invalidation.
  return state.anchorPosition?.let { anchorPosition ->
    (anchorPosition - state.config.initialLoadSize / 2).coerceAtLeast(0)
  }
}

مدیریت کردن خطاها

درخواست‌های بار کردن داده‌ها ممکن است به دلایل مختلفی ناموفق باشد، به‌ویژه هنگام بار کردن ازطریق شبکه. خطاهای رخ‌داده درطول بار کردن را با برگرداندن شیء LoadResult.Error از روش load گزارش کنید.

برای مثال، می‌توانید با افزودن موارد زیر به روش load، خطاهای بار کردن را در ExamplePagingSource از مثال قبلی بگیرید و گزارش کنید:

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 نشان می‌دهد. کتابخانه «صفحه‌بندی» جریان داده‌ها را به‌صورت Flow نمایش می‌دهد.

وقتی نمونه Pager را برای راه‌اندازی جاری‌سازی واکنشی ایجاد می‌کنید، باید شیء پیکربندی PagingConfig و تابعی را که به Pager می‌گوید چگونه نمونه‌ای از پیاده‌سازی PagingSource شما را دریافت کند، به نمونه ارائه دهید. همچنین می‌توانید initialKey اختیاری را مشخص کنید، مثلاً 1 برای شماره صفحه‌های مبتنی بر ۱ یا 0 برای صفحه‌بندی مبتنی بر آفست. در PagingConfig، می‌توانید pageSize،‏ initialLoadSize،‏ prefetchDistance، و enablePlaceholders را پیکربندی کنید. ویژگی prefetchDistance مشخص می‌کند چند مورد از هریک از لبه‌های محتوای بارشده واکشی بعدی را راه‌اندازی می‌کند.

class UserViewModel(
    private val backend: ExampleBackendService,
    private val query: String
) : ViewModel() {

    val userPagingFlow: Flow<PagingData<User>> = Pager(
        config = PagingConfig(
            pageSize = 20,
            // Load 3 pages initially so the user can scroll immediately without
            // hitting a loading state.
            initialLoadSize = 60,
            // Fetch the next page far enough ahead of the scroll position to
            // avoid visible pauses.
            prefetchDistance = 20,
            enablePlaceholders = true
        ),
        initialKey = 1,
        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()
            }
        }
    }
}

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

اکنون فهرست داده‌های صفحه‌بندی‌شده را نمایش می‌دهد و کتابخانه «صفحه‌بندی» با پیمایش کاربر، صفحه‌های اضافی را بار می‌کند.

منابع بیشتر

برای کسب اطلاعات بیشتر درباره کتابخانه Paging، منابع اضافی زیر را ببینید:

اسناد

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