تحويل مصادر البيانات

عند العمل مع بيانات مقسّمة إلى صفحات، غالبًا ما تحتاج إلى تحويل مصدر البيانات أثناء تحميله. على سبيل المثال، قد تحتاج إلى فلترة قائمة عناصر أو تحويل العناصر إلى نوع مختلف قبل عرضها في واجهة المستخدم. حالة الاستخدام الشائعة الأخرى لتحويل ساحة مشاركة البيانات هي إضافة فواصل بين العناصر في القائمة.

بشكل عام، يتيح لك تطبيق عمليات التحويل مباشرةً على مصدر البيانات إبقاء بنى المستودع وبنى واجهة المستخدم منفصلة.

تفترض هذه الصفحة أنّك على دراية بالاستخدام الأساسي لمكتبة Paging.

تطبيق التحويلات الأساسية

بما أنّ PagingData مضمّن في مصدر بيانات تفاعلي، يمكنك تطبيق عمليات تحويل على البيانات بشكل تدريجي بين تحميل البيانات وعرضها.

لتطبيق عمليات تحويل على كل عنصر PagingData في البث، ضَع عمليات التحويل داخل عملية map() على البث:

pager.flow // Type is Flow<PagingData<User>>.
  // Map the outer stream so that the transformations are applied to
  // each new generation of PagingData.
  .map { pagingData ->
    // Transformations in this block are applied to the items
    // in the paged data.
}

تحويل البيانات

إنّ أبسط عملية يمكن إجراؤها على سلسلة من البيانات هي تحويلها إلى نوع مختلف. بعد الحصول على إذن الوصول إلى العنصر PagingData، يمكنك تنفيذ عملية map() على كل عنصر فردي في القائمة المقسّمة إلى صفحات ضمن العنصر PagingData.

أحد حالات الاستخدام الشائعة لذلك هو ربط عنصر من طبقة الشبكة أو قاعدة البيانات بعنصر مستخدَم تحديدًا في طبقة واجهة المستخدم. يوضّح المثال أدناه كيفية تطبيق هذا النوع من عمليات الربط:

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.map { user -> UiModel(user) }
  }

من عمليات تحويل البيانات الشائعة الأخرى تلقّي إدخال من المستخدم، مثل سلسلة طلب بحث، وتحويله إلى ناتج الطلب لعرضه. يتطلّب إعداد ذلك الاستماع إلى طلب البحث الذي أدخله المستخدم وتسجيله، وتنفيذ الطلب، وإرسال نتيجة طلب البحث إلى واجهة المستخدم.

يمكنك الاستماع إلى إدخال طلب البحث باستخدام واجهة برمجة تطبيقات للبث. احتفظوا بمرجع البث في ViewModel. يجب ألا تتمكّن طبقة واجهة المستخدم من الوصول إليها مباشرةً، بل يجب تحديد دالة لإعلام ViewModel بطلب البحث الذي أدخله المستخدم.

private val queryFlow = MutableStateFlow("")

fun onQueryChanged(query: String) {
  queryFlow.value = query
}

عندما تتغيّر قيمة طلب البحث في مصدر البيانات، يمكنك تنفيذ عمليات لتحويل قيمة طلب البحث إلى نوع البيانات المطلوب وإرجاع النتيجة إلى طبقة واجهة المستخدم. تعتمد وظيفة التحويل المحدّدة على اللغة وإطار العمل المستخدَمَين، ولكنها توفّر جميعًا وظائف مشابهة.

val querySearchResults: Flow<User> = queryFlow.flatMapLatest { query ->
  // The database query returns a Flow which is output through
  // querySearchResults
  userDatabase.searchBy(query)
}

يضمن استخدام عمليات مثل flatMapLatest أو switchMap عرض أحدث النتائج فقط في واجهة المستخدم. إذا غيّر المستخدم طلب البحث قبل اكتمال عملية قاعدة البيانات، ستتجاهل هذه العمليات النتائج من طلب البحث القديم وتبدأ البحث الجديد على الفور.

فلترة البيانات

هناك عملية شائعة أخرى وهي الفلترة. يمكنك فلترة البيانات استنادًا إلى معايير من المستخدم، أو يمكنك إزالة البيانات من واجهة المستخدم إذا كان من المفترض إخفاؤها استنادًا إلى معايير أخرى.

عليك وضع عمليات الفلترة هذه داخل استدعاء map() لأنّ الفلتر ينطبق على العنصر PagingData. بعد فلترة البيانات من PagingData، يتم تمرير مثيل PagingData الجديد إلى طبقة واجهة المستخدم لعرضه.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
  }

إضافة فواصل القوائم

تتيح مكتبة Paging استخدام فواصل ديناميكية بين عناصر القائمة. يمكنك تحسين قابلية قراءة القائمة من خلال إدراج فواصل مباشرةً في مصدر البيانات كعناصر قابلة للإنشاء في التصميم. نتيجةً لذلك، أصبحت الفواصل عناصر قابلة للإنشاء تتضمّن جميع الميزات، ما يتيح التفاعل الكامل والتصميم والدلالات المتعلقة بتسهيل الاستخدام.

هناك ثلاث خطوات لإدراج فواصل في قائمتك المقسّمة إلى صفحات:

  1. حوِّل نموذج واجهة المستخدم لاستيعاب عناصر الفواصل. إحدى طرق إجراء ذلك هي تضمين عنصر البيانات وفاصله في فئة محكمة الإغلاق واحدة. ويتيح ذلك لواجهة المستخدم التعامل مع أنواع متعددة من العناصر في القائمة نفسها.
  2. حوِّل مصدر البيانات لإضافة الفواصل بين تحميل البيانات وعرضها بشكل ديناميكي.
  3. تعديل واجهة المستخدم للتعامل مع عناصر الفواصل

تحويل نموذج واجهة المستخدم

تُدرِج مكتبة Paging فواصل القوائم في واجهة المستخدم كعناصر قائمة فعلية، ولكن يجب أن تكون عناصر الفواصل قابلة للتمييز عن عناصر البيانات في القائمة للتأكّد من عرض كلا النوعين من العناصر القابلة للإنشاء بشكل مميز. الحلّ هو إنشاء فئة Kotlin محكمة الإغلاق مع فئات فرعية لتمثيل بياناتك والفواصل. بدلاً من ذلك، يمكنك إنشاء صنف أساسي يتم توسيعه من خلال فئة عناصر القائمة وفئة الفواصل.

لنفترض أنّك تريد إضافة فواصل إلى قائمة مقسّمة إلى صفحات تتضمّن User عنصرًا. يوضّح المقتطف التالي كيفية إنشاء صنف أساسي يمكن أن تكون مثيلاته إما UserModel أو SeparatorModel:

sealed class UiModel {
  class UserModel(val id: String, val label: String) : UiModel() {
    constructor(user: User) : this(user.id, user.label)
  }

  class SeparatorModel(val description: String) : UiModel()
}

تحويل مصدر البيانات

يجب تطبيق عمليات التحويل على مصدر البيانات بعد تحميله وقبل عرضه. يجب أن تنفّذ عمليات التحويل ما يلي:

  • حوِّل عناصر القائمة التي تم تحميلها لتعكس نوع العنصر الأساسي الجديد.
  • استخدِم طريقة PagingData.insertSeparators() لإضافة الفواصل.

لمزيد من المعلومات عن عمليات التحويل، يُرجى الاطّلاع على مقالة تطبيق عمليات التحويل الأساسية.

يوضّح المثال التالي عمليات التحويل لتعديل PagingData<User> إلى PagingData<UiModel> مع إضافة فواصل:

pager.flow.map { pagingData: PagingData<User> ->
  // Map outer stream, so you can perform transformations on
  // each paging generation.
  pagingData
  .map { user ->
    // Convert items in stream to UiModel.UserModel.
    UiModel.UserModel(user)
  }
  .insertSeparators<UiModel.UserModel, UiModel> { before, after ->
    when {
      before == null -> UiModel.SeparatorModel("HEADER")
      after == null -> UiModel.SeparatorModel("FOOTER")
      shouldSeparate(before, after) -> UiModel.SeparatorModel(
        "BETWEEN ITEMS $before AND $after"
      )
      // Return null to avoid adding a separator between two items.
      else -> null
    }
  }
}

التعامل مع الفواصل في واجهة المستخدم

الخطوة الأخيرة هي تغيير واجهة المستخدم لتلائم نوع العنصر الفاصل. في التصميم الكسول، يمكنك التعامل مع أنواع عناصر متعددة من خلال التحقّق من نوع كل UiModel تم إصداره. عند تكرار البيانات المقسّمة إلى صفحات، استخدِم عبارة when لاستدعاء العنصر القابل للإنشاء المناسب. يتيح لك ذلك توفير واجهة مستخدم مميّزة لعناصر البيانات والفواصل.

@Composable fun UserList(pagingItems: LazyPagingItems) {
  LazyColumn {
    items(
      count = pagingItems.itemCount,
      key = { index ->
        val item = pagingItems.peek(index)
        when (item) {
          is UiModel.UserModel -> item.user.id
          is UiModel.SeparatorModel -> item.description
          else -> index
        }
      }
    ) { index ->
      when (val item = pagingItems[index]) {
        is UiModel.UserModel -> UserItemComposable(item.user)
        is UiModel.SeparatorModel -> SeparatorComposable(item.description)
        null -> PlaceholderComposable()
      }
    }
  }
}

تجنُّب العمل المكرّر

من المشاكل الرئيسية التي يجب تجنُّبها أن ينفّذ التطبيق مهامًا غير ضرورية. يُعدّ استرجاع البيانات عملية مكلفة، ويمكن أن تستغرق عمليات تحويل البيانات وقتًا طويلاً. بعد تحميل البيانات وإعدادها للعرض في واجهة المستخدم، يجب حفظها في حال حدوث تغيير في الإعدادات واحتجنا إلى إعادة إنشاء واجهة المستخدم.

تخزّن عملية cachedIn() النتائج في ذاكرة التخزين المؤقت لأي عمليات تحويل تحدث قبلها. عادةً، يتم تطبيق عامل التشغيل هذا في ViewModel قبل عرض Flow على العناصر القابلة للإنشاء.

لإدارة ذاكرة التخزين المؤقت بشكل صحيح، مرِّر CoroutineScope إلى cachedIn()، كما هو موضّح في المثال التالي باستخدام viewModelScope.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
      .map { user -> UiModel.UserModel(user) }
  }
  .cachedIn(viewModelScope)

لمزيد من المعلومات حول استخدام cachedIn() مع مصدر PagingData، راجِع مقالة إعداد مصدر PagingData.

مراجع إضافية

لمزيد من المعلومات حول مكتبة Paging، يُرجى الاطّلاع على المراجع الإضافية التالية:

المستندات

مشاهدة المحتوى