তথ্য স্ট্রীম রূপান্তর

যখন আপনি পেজড ডেটা নিয়ে কাজ করেন , তখন ডেটা স্ট্রিম লোড করার সময় প্রায়শই সেটিকে রূপান্তর করার প্রয়োজন হয়। উদাহরণস্বরূপ, UI-তে দেখানোর আগে আপনার আইটেমের একটি তালিকা ফিল্টার করার বা আইটেমগুলিকে অন্য কোনো ধরনে রূপান্তর করার প্রয়োজন হতে পারে। ডেটা স্ট্রিম রূপান্তরের আরেকটি সাধারণ ব্যবহার হলো তালিকায় বিভাজক (list separator) যোগ করা

আরও সাধারণভাবে বলতে গেলে, ডেটা স্ট্রিমে সরাসরি রূপান্তর প্রয়োগ করার মাধ্যমে আপনি আপনার রিপোজিটরি কাঠামো এবং UI কাঠামোকে আলাদা রাখতে পারেন।

এই পৃষ্ঠাটি ধরে নেয় যে আপনি পেজিং লাইব্রেরির প্রাথমিক ব্যবহার সম্পর্কে অবগত আছেন।

মৌলিক রূপান্তর প্রয়োগ করুন

যেহেতু 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 অবজেক্টটি অ্যাক্সেস করতে পারলে, আপনি PagingData অবজেক্টের মধ্যে থাকা পেজ করা তালিকার প্রতিটি আইটেমের উপর একটি map() অপারেশন সম্পাদন করতে পারেন।

এর একটি সাধারণ ব্যবহার হলো নেটওয়ার্ক বা ডাটাবেস লেয়ারের কোনো অবজেক্টকে UI লেয়ারে বিশেষভাবে ব্যবহৃত কোনো অবজেক্টের সাথে ম্যাপ করা। নিচের উদাহরণটিতে দেখানো হয়েছে কীভাবে এই ধরনের ম্যাপ অপারেশন প্রয়োগ করতে হয়:

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

আরেকটি সাধারণ ডেটা রূপান্তর হলো ব্যবহারকারীর কাছ থেকে কোয়েরি স্ট্রিং-এর মতো কোনো ইনপুট নিয়ে, সেটিকে প্রদর্শনের জন্য রিকোয়েস্ট আউটপুটে রূপান্তর করা। এটি সেট আপ করার জন্য ব্যবহারকারীর কোয়েরি ইনপুট শোনা ও গ্রহণ করা, রিকোয়েস্টটি সম্পাদন করা এবং কোয়েরির ফলাফল UI-তে ফেরত পাঠানো প্রয়োজন হয়।

আপনি একটি স্ট্রিম এপিআই ব্যবহার করে কোয়েরি ইনপুটের জন্য অপেক্ষা করতে পারেন। স্ট্রিম রেফারেন্সটি আপনার ViewModel এ রাখুন। UI লেয়ারের এতে সরাসরি অ্যাক্সেস থাকা উচিত নয়; পরিবর্তে, ব্যবহারকারীর কোয়েরি সম্পর্কে ViewModel-কে অবহিত করার জন্য একটি ফাংশন সংজ্ঞায়িত করুন।

private val queryFlow = MutableStateFlow("")

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

ডেটা স্ট্রিমে কোয়েরি ভ্যালু পরিবর্তিত হলে, আপনি কোয়েরি ভ্যালুটিকে কাঙ্ক্ষিত ডেটা টাইপে রূপান্তর করার জন্য অপারেশন সম্পাদন করতে পারেন এবং ফলাফলটি UI লেয়ারে ফেরত পাঠাতে পারেন। নির্দিষ্ট রূপান্তর ফাংশনটি ব্যবহৃত ভাষা এবং ফ্রেমওয়ার্কের উপর নির্ভর করে, তবে তারা সকলেই একই ধরনের কার্যকারিতা প্রদান করে।

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

flatMapLatest বা switchMap মতো অপারেশন ব্যবহার করলে UI-তে শুধুমাত্র সর্বশেষ ফলাফলগুলোই দেখানো নিশ্চিত হয়। ডাটাবেস অপারেশনটি সম্পন্ন হওয়ার আগে যদি ব্যবহারকারী তার কোয়েরি ইনপুট পরিবর্তন করেন, তবে এই অপারেশনগুলো পুরোনো কোয়েরির ফলাফল বাতিল করে দেয় এবং সাথে সাথে নতুন সার্চটি চালু করে।

ডেটা ফিল্টার করুন

আরেকটি সাধারণ অপারেশন হলো ফিল্টারিং। আপনি ব্যবহারকারীর দেওয়া শর্তের উপর ভিত্তি করে ডেটা ফিল্টার করতে পারেন, অথবা অন্য কোনো শর্তের উপর ভিত্তি করে ডেটা লুকানোর প্রয়োজন হলে তা UI থেকে সরিয়ে দিতে পারেন।

আপনাকে এই ফিল্টার অপারেশনগুলো map() কলের ভিতরে রাখতে হবে, কারণ ফিল্টারটি PagingData অবজেক্টের উপর প্রয়োগ করা হয়। PagingData থেকে ডেটা ফিল্টার করে বের করে নেওয়ার পর, নতুন PagingData ইনস্ট্যান্সটি প্রদর্শনের জন্য UI লেয়ারে পাঠানো হয়।

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

তালিকার বিভাজক যোগ করুন

পেজিং লাইব্রেরি ডাইনামিক লিস্ট সেপারেটর সমর্থন করে। আপনি আপনার লেআউটে কম্পোজেবল হিসেবে সরাসরি ডেটা স্ট্রিমে সেপারেটর সন্নিবেশ করে তালিকার পাঠযোগ্যতা উন্নত করতে পারেন। ফলস্বরূপ, সেপারেটরগুলো পূর্ণাঙ্গ কম্পোজেবলে পরিণত হয়, যা সম্পূর্ণ ইন্টারঅ্যাকটিভিটি, স্টাইলিং এবং অ্যাক্সেসিবিলিটি সিম্যান্টিকস সক্ষম করে।

আপনার পৃষ্ঠাযুক্ত তালিকায় বিভাজক যুক্ত করার জন্য তিনটি ধাপ রয়েছে:

  1. সেপারেটর আইটেমগুলোকে অন্তর্ভুক্ত করার জন্য UI মডেলটি রূপান্তর করুন। এটি করার একটি উপায় হলো আপনার ডেটা আইটেম এবং সেপারেটরকে একটিমাত্র সিলড ক্লাসের মধ্যে রাখা। এর ফলে UI একই লিস্টে একাধিক ধরনের আইটেম পরিচালনা করতে পারে।
  2. ডেটা লোড করা এবং উপস্থাপন করার মধ্যবর্তী সময়ে গতিশীলভাবে বিভাজক যোগ করতে ডেটা স্ট্রিমকে রূপান্তর করুন।
  3. বিভাজক আইটেমগুলো পরিচালনা করার জন্য UI আপডেট করুন।

UI মডেলটি রূপান্তর করুন

পেজিং লাইব্রেরি লিস্ট সেপারেটরগুলোকে UI-তে প্রকৃত লিস্ট আইটেম হিসেবে যুক্ত করে, কিন্তু লিস্টের ডেটা আইটেমগুলো থেকে সেপারেটর আইটেমগুলোকে অবশ্যই আলাদা হতে হবে, যাতে উভয় কম্পোজেবল টাইপ স্বতন্ত্রভাবে রেন্ডার হয়। এর সমাধান হলো, আপনার ডেটা এবং সেপারেটরগুলোকে উপস্থাপন করার জন্য সাবক্লাসসহ একটি কোটলিন সিলড ক্লাস তৈরি করা। বিকল্পভাবে, আপনি একটি বেস ক্লাস তৈরি করতে পারেন যা আপনার লিস্ট আইটেম ক্লাস এবং সেপারেটর ক্লাস দ্বারা এক্সটেন্ড করা হবে।

ধরুন, আপনি 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-তে বিভাজকগুলি পরিচালনা করুন

চূড়ান্ত ধাপটি হলো সেপারেটর আইটেম টাইপকে অন্তর্ভুক্ত করার জন্য আপনার UI পরিবর্তন করা। একটি লেজি লেআউটে, আপনি প্রতিটি এমিটেড UiModel এর টাইপ পরীক্ষা করে একাধিক আইটেম টাইপ পরিচালনা করতে পারেন। আপনার পেজড ডেটার মধ্যে দিয়ে ইটারেট করার সময়, উপযুক্ত কম্পোজেবলকে কল করার জন্য একটি when স্টেটমেন্ট ব্যবহার করুন। এটি আপনাকে ডেটা আইটেম এবং সেপারেটরগুলির জন্য একটি স্বতন্ত্র UI প্রদান করতে দেয়।

@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()
      }
    }
  }
}

একই কাজ বারবার করা থেকে বিরত থাকুন

একটি গুরুত্বপূর্ণ বিষয় যা এড়িয়ে চলতে হবে তা হলো অ্যাপকে দিয়ে অপ্রয়োজনীয় কাজ করানো। ডেটা সংগ্রহ করা একটি ব্যয়বহুল প্রক্রিয়া, এবং ডেটা রূপান্তরেও মূল্যবান সময় লাগতে পারে। ডেটা লোড হয়ে UI-তে প্রদর্শনের জন্য প্রস্তুত হয়ে গেলে, তা সংরক্ষণ করা উচিত, যাতে কোনো কনফিগারেশন পরিবর্তনের ফলে UI পুনরায় তৈরি করার প্রয়োজন হলে তা কাজে লাগে।

` cachedIn() ` অপারেশনটি এর আগে সংঘটিত যেকোনো রূপান্তরের ফলাফল ক্যাশ করে রাখে। সাধারণত, আপনার কম্পোজেবলগুলোতে Flow প্রকাশ করার আগে, আপনি আপনার ViewModel এই অপারেটরটি প্রয়োগ করেন।

ক্যাশে সঠিকভাবে পরিচালনা করতে, cachedIn() -এ একটি CoroutineScope পাস করুন, যেমনটি নিচের উদাহরণে viewModelScope ব্যবহার করে দেখানো হয়েছে।

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

PagingData এর একটি স্ট্রিমের সাথে cachedIn() ব্যবহারের বিষয়ে আরও তথ্যের জন্য, "PagingData-এর একটি স্ট্রিম সেট আপ করুন" দেখুন।

অতিরিক্ত সম্পদ

পেজিং লাইব্রেরি সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্সগুলো দেখুন:

ডকুমেন্টেশন

বিষয়বস্তু দেখুন

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}