جریان های داده را تغییر دهید

وقتی با داده‌های صفحه‌بندی‌شده کار می‌کنید ، اغلب نیاز دارید که جریان داده را هنگام بارگذاری آن تغییر دهید. برای مثال، ممکن است لازم باشد لیستی از موارد را فیلتر کنید یا قبل از نمایش آنها در رابط کاربری، موارد را به نوع دیگری تبدیل کنید. یکی دیگر از موارد استفاده رایج برای تبدیل جریان داده، اضافه کردن جداکننده‌های لیست است.

به طور کلی، اعمال مستقیم تبدیل‌ها به جریان داده به شما این امکان را می‌دهد که ساختارهای مخزن و ساختارهای رابط کاربری خود را از هم جدا نگه دارید.

این صفحه فرض می‌کند که شما با استفاده اولیه از کتابخانه 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) }
  }

یکی دیگر از تبدیل‌های رایج داده‌ها، دریافت ورودی از کاربر، مانند یک رشته پرس‌وجو، و تبدیل آن به خروجی درخواست برای نمایش است. تنظیم این مورد مستلزم گوش دادن و دریافت ورودی پرس‌وجوی کاربر، انجام درخواست و ارسال نتیجه پرس‌وجو به رابط کاربری است.

شما می‌توانید با استفاده از یک API جریانی (stream API) به ورودی کوئری گوش دهید. مرجع جریان را در ViewModel خود نگه دارید. لایه رابط کاربری (UI layer) نباید مستقیماً به آن دسترسی داشته باشد؛ در عوض، تابعی تعریف کنید تا 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 برای نمایش به لایه UI ارسال می‌شود.

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

جداکننده‌های لیست را اضافه کنید

کتابخانه Paging از جداکننده‌های پویای لیست پشتیبانی می‌کند. شما می‌توانید با وارد کردن جداکننده‌ها به طور مستقیم در جریان داده‌ها به عنوان composableها در طرح‌بندی خود، خوانایی لیست را بهبود بخشید. در نتیجه، جداکننده‌ها composableهای کاملاً کاربردی هستند که امکان تعامل کامل، استایل‌بندی و دسترسی‌پذیری را فراهم می‌کنند.

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

  1. مدل رابط کاربری را برای تطبیق با آیتم‌های جداکننده تبدیل کنید. یک راه برای انجام این کار، قرار دادن آیتم داده و جداکننده در یک کلاس مهر و موم شده است. این به رابط کاربری اجازه می‌دهد تا چندین نوع آیتم را در یک لیست مدیریت کند.
  2. جریان داده را طوری تغییر دهید که به صورت پویا جداکننده‌ها را بین بارگذاری داده‌ها و ارائه داده‌ها اضافه کند.
  3. رابط کاربری را برای مدیریت آیتم‌های جداکننده به‌روزرسانی کنید.

تبدیل مدل رابط کاربری

کتابخانه Paging جداکننده‌های لیست را به عنوان آیتم‌های لیست واقعی در رابط کاربری وارد می‌کند، اما آیتم‌های جداکننده باید از آیتم‌های داده موجود در لیست قابل تشخیص باشند تا مطمئن شویم که هر دو نوع قابل ترکیب به طور مجزا رندر می‌شوند. راه حل این است که یک کلاس مهر و موم شده کاتلین با زیرکلاس‌هایی برای نمایش داده‌ها و جداکننده‌های خود ایجاد کنید. به عنوان یک جایگزین، می‌توانید یک کلاس پایه ایجاد کنید که توسط کلاس آیتم لیست و کلاس جداکننده شما توسعه داده شود.

فرض کنید می‌خواهید جداکننده‌هایی را به یک لیست صفحه‌بندی‌شده از آیتم‌های 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
    }
  }
}

مدیریت جداکننده‌ها در رابط کاربری

مرحله آخر، تغییر رابط کاربری (UI) برای تطبیق با نوع آیتم جداکننده است. در یک طرح‌بندی تنبل (lazy layout)، می‌توانید چندین نوع آیتم را با بررسی نوع هر UiModel منتشر شده مدیریت کنید. هنگام پیمایش (iteration) در داده‌های صفحه‌بندی شده، از یک دستور when برای فراخوانی composable مناسب استفاده کنید. این به شما امکان می‌دهد یک رابط کاربری متمایز برای آیتم‌های داده و جداکننده‌ها فراهم کنید.

@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() نتایج هرگونه تبدیلی را که قبل از آن رخ می‌دهد، ذخیره می‌کند. معمولاً این عملگر را قبل از نمایش Flow در composables خود، در ViewModel اعمال می‌کنید.

برای مدیریت صحیح حافظه پنهان، یک 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، به منابع اضافی زیر مراجعه کنید:

مستندات

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

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}