ডেটা স্ট্রিম (ভিউ) রূপান্তর করুন

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

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

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

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

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

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

স্ট্রিমে থাকা প্রতিটি PagingData অবজেক্টে ট্রান্সফরমেশন প্রয়োগ করার জন্য, স্ট্রিমে একটি map() অপারেশনের ভিতরে ট্রান্সফরমেশনগুলো রাখুন:

জাভা

PagingRx.getFlowable(pager) // Type is Flowable<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.
  });

জাভা

// Map the outer stream so that the transformations are applied to
// each new generation of PagingData.
Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData -> {
    // Transformations in this block are applied to the items
    // in the paged data.
  });

ডেটা রূপান্তর করুন

ডেটা স্ট্রিমের উপর সবচেয়ে মৌলিক অপারেশন হলো এটিকে একটি ভিন্ন প্রকারে রূপান্তর করা। একবার আপনি PagingData অবজেক্টটি অ্যাক্সেস করতে পারলে, আপনি PagingData অবজেক্টের মধ্যে থাকা পেজ করা তালিকার প্রতিটি আইটেমের উপর একটি map() অপারেশন সম্পাদন করতে পারেন।

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

জাভা

// Type is Flowable<PagingData<User>>.
PagingRx.getFlowable(pager)
  .map(pagingData ->
    pagingData.map(UiModel.UserModel::new)
  )

জাভা

Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData ->
    pagingData.map(UiModel.UserModel::new)
)

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

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

জাভা

private BehaviorSubject<String> querySubject = BehaviorSubject.create("");

public void onQueryChanged(String query) {
  queryFlow.onNext(query)
}

জাভা

private MutableLiveData<String> queryLiveData = new MutableLiveData("");

public void onQueryChanged(String query) {
  queryFlow.setValue(query)
}

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

জাভা

Observable<User> querySearchResults =
  querySubject.switchMap(query -> userDatabase.searchBy(query));

জাভা

LiveData<User> querySearchResults = Transformations.switchMap(
  queryLiveData,
  query -> userDatabase.searchBy(query)
);

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

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

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

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

জাভা

// Type is Flowable<PagingData<User>>.
PagingRx.getFlowable(pager)
  .map(pagingData ->
    pagingData.filter(user -> !user.isHiddenFromUi())
  )
}

জাভা

Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData ->
    pagingData.filter(user -> !user.isHiddenFromUi())
)

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

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

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

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

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

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

ধরুন, আপনি User আইটেমগুলোর একটি পেজড লিস্টে সেপারেটর যোগ করতে চান। নিচের কোড স্নিপেটটিতে দেখানো হয়েছে কীভাবে একটি বেস ক্লাস তৈরি করতে হয়, যেখানে ইনস্ট্যান্সগুলো UserModel অথবা SeparatorModel হতে পারে:

জাভা

class UiModel {
  private UiModel() {}

  static class UserModel extends UiModel {
    @NonNull
    private String mId;
    @NonNull
    private String mLabel;

    UserModel(@NonNull String id, @NonNull String label) {
      mId = id;
      mLabel = label;
    }

    UserModel(@NonNull User user) {
      mId = user.id;
      mLabel = user.label;
    }

    @NonNull
    public String getId() {
      return mId;
    }

    @NonNull
    public String getLabel() {
      return mLabel;
      }
    }

    static class SeparatorModel extends UiModel {
    @NonNull
    private String mDescription;

    SeparatorModel(@NonNull String description) {
      mDescription = description;
    }

    @NonNull
    public String getDescription() {
      return mDescription;
    }
  }
}

জাভা

class UiModel {
  private UiModel() {}

  static class UserModel extends UiModel {
    @NonNull
    private String mId;
    @NonNull
    private String mLabel;

    UserModel(@NonNull String id, @NonNull String label) {
      mId = id;
      mLabel = label;
    }

    UserModel(@NonNull User user) {
      mId = user.id;
      mLabel = user.label;
    }

    @NonNull
    public String getId() {
      return mId;
    }

    @NonNull
    public String getLabel() {
      return mLabel;
      }
    }

    static class SeparatorModel extends UiModel {
    @NonNull
    private String mDescription;

    SeparatorModel(@NonNull String description) {
      mDescription = description;
    }

    @NonNull
    public String getDescription() {
      return mDescription;
    }
  }
}

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

ডেটা স্ট্রিমটি লোড করার পর এবং উপস্থাপন করার আগে আপনাকে অবশ্যই এতে রূপান্তর প্রয়োগ করতে হবে। এই রূপান্তরগুলো নিম্নলিখিত কাজগুলো করবে:

  • লোড করা তালিকার আইটেমগুলোকে নতুন মূল আইটেমের ধরন অনুযায়ী রূপান্তর করুন।
  • সেপারেটরগুলো যোগ করতে PagingData.insertSeparators() মেথডটি ব্যবহার করুন।

রূপান্তর অপারেশন সম্পর্কে আরও জানতে, মৌলিক রূপান্তর প্রয়োগ করুন দেখুন।

নিম্নলিখিত উদাহরণটি PagingData<User> স্ট্রিমকে বিভাজক যুক্ত PagingData<UiModel> স্ট্রিমে আপডেট করার জন্য রূপান্তর অপারেশনগুলি দেখায়:

জাভা

// Map outer stream, so you can perform transformations on each
// paging generation.
PagingRx.getFlowable(pager).map(pagingData -> {
  // First convert items in stream to UiModel.UserModel.
  PagingData<UiModel> uiModelPagingData = pagingData.map(
    UiModel.UserModel::new);

  // Insert UiModel.SeparatorModel, which produces PagingData of
  // generic type UiModel.
  return PagingData.insertSeparators(uiModelPagingData,
    (@Nullable UiModel before, @Nullable UiModel after) -> {
      if (before == null) {
        return new UiModel.SeparatorModel("HEADER");
      } else if (after == null) {
        return new UiModel.SeparatorModel("FOOTER");
      } else if (shouldSeparate(before, after)) {
        return new UiModel.SeparatorModel("BETWEEN ITEMS "
          + before.toString() + " AND " + after.toString());
      } else {
        // Return null to avoid adding a separator between two
        // items.
        return null;
      }
    });
});

জাভা

// Map outer stream, so you can perform transformations on each
// paging generation.
Transformations.map(PagingLiveData.getLiveData(pager),
  pagingData -> {
    // First convert items in stream to UiModel.UserModel.
    PagingData<UiModel> uiModelPagingData = pagingData.map(
      UiModel.UserModel::new);

    // Insert UiModel.SeparatorModel, which produces PagingData of
    // generic type UiModel.
    return PagingData.insertSeparators(uiModelPagingData,
      (@Nullable UiModel before, @Nullable UiModel after) -> {
        if (before == null) {
          return new UiModel.SeparatorModel("HEADER");
        } else if (after == null) {
          return new UiModel.SeparatorModel("FOOTER");
        } else if (shouldSeparate(before, after)) {
          return new UiModel.SeparatorModel("BETWEEN ITEMS "
            + before.toString() + " AND " + after.toString());
        } else {
          // Return null to avoid adding a separator between two
          // items.
          return null;
        }
      });
  });

UI-তে বিভাজকগুলি পরিচালনা করুন

চূড়ান্ত ধাপটি হলো সেপারেটর আইটেম টাইপকে অন্তর্ভুক্ত করার জন্য আপনার UI পরিবর্তন করা। আপনার সেপারেটর আইটেমগুলোর জন্য একটি লেআউট এবং একটি ভিউ হোল্ডার তৈরি করুন এবং লিস্ট অ্যাডাপ্টারটিকে পরিবর্তন করে এর ভিউ হোল্ডার টাইপ হিসেবে RecyclerView.ViewHolder ব্যবহার করুন, যাতে এটি একাধিক ধরনের ভিউ হোল্ডার পরিচালনা করতে পারে। বিকল্পভাবে, আপনি একটি সাধারণ বেস ক্লাস সংজ্ঞায়িত করতে পারেন যা আপনার আইটেম এবং সেপারেটর ভিউ হোল্ডার ক্লাস উভয়ই এক্সটেন্ড করবে।

আপনাকে আপনার লিস্ট অ্যাডাপ্টারে নিম্নলিখিত পরিবর্তনগুলিও করতে হবে:

  • সেপারেটর লিস্ট আইটেমগুলো অন্তর্ভুক্ত করার জন্য onCreateViewHolder() এবং onBindViewHolder() মেথডগুলোতে কেস যোগ করুন।
  • একটি নতুন তুলনাকারী প্রয়োগ করুন।

জাভা

class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> {
  UiModelAdapter() {
    super(new UiModelComparator(), Dispatchers.getMain(),
      Dispatchers.getDefault());
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
    int viewType) {
    if (viewType == R.layout.item) {
      return new UserModelViewHolder(parent);
    } else {
      return new SeparatorModelViewHolder(parent);
    }
  }

  @Override
  public int getItemViewType(int position) {
    // Use peek over getItem to avoid triggering page fetch / drops, since
    // recycling views is not indicative of the user's current scroll position.
    UiModel item = peek(position);
    if (item instanceof UiModel.UserModel) {
      return R.layout.item;
    } else if (item instanceof UiModel.SeparatorModel) {
      return R.layout.separator_item;
    } else {
      throw new IllegalStateException("Unknown view");
    }
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
    int position) {
    if (holder instanceOf UserModelViewHolder) {
      UserModel userModel = (UserModel) getItem(position);
      ((UserModelViewHolder) holder).bind(userModel);
    } else {
      SeparatorModel separatorModel = (SeparatorModel) getItem(position);
      ((SeparatorModelViewHolder) holder).bind(separatorModel);
    }
  }
}

class UiModelComparator extends DiffUtil.ItemCallback<UiModel> {
  @Override
  public boolean areItemsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    boolean isSameRepoItem = oldItem instanceof UserModel
      && newItem instanceof UserModel
      && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId());

    boolean isSameSeparatorItem = oldItem instanceof SeparatorModel
      && newItem instanceof SeparatorModel
      && ((SeparatorModel) oldItem).getDescription().equals(
      ((SeparatorModel) newItem).getDescription());

    return isSameRepoItem || isSameSeparatorItem;
  }

  @Override
  public boolean areContentsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    return oldItem.equals(newItem);
  }
}

জাভা

class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> {
  UiModelAdapter() {
    super(new UiModelComparator(), Dispatchers.getMain(),
      Dispatchers.getDefault());
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
    int viewType) {
    if (viewType == R.layout.item) {
      return new UserModelViewHolder(parent);
    } else {
      return new SeparatorModelViewHolder(parent);
    }
  }

  @Override
  public int getItemViewType(int position) {
    // Use peek over getItem to avoid triggering page fetch / drops, since
    // recycling views is not indicative of the user's current scroll position.
    UiModel item = peek(position);
    if (item instanceof UiModel.UserModel) {
      return R.layout.item;
    } else if (item instanceof UiModel.SeparatorModel) {
      return R.layout.separator_item;
    } else {
      throw new IllegalStateException("Unknown view");
    }
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
    int position) {
    if (holder instanceOf UserModelViewHolder) {
      UserModel userModel = (UserModel) getItem(position);
      ((UserModelViewHolder) holder).bind(userModel);
    } else {
      SeparatorModel separatorModel = (SeparatorModel) getItem(position);
      ((SeparatorModelViewHolder) holder).bind(separatorModel);
    }
  }
}

class UiModelComparator extends DiffUtil.ItemCallback<UiModel> {
  @Override
  public boolean areItemsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    boolean isSameRepoItem = oldItem instanceof UserModel
      && newItem instanceof UserModel
      && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId());

    boolean isSameSeparatorItem = oldItem instanceof SeparatorModel
      && newItem instanceof SeparatorModel
      && ((SeparatorModel) oldItem).getDescription().equals(
      ((SeparatorModel) newItem).getDescription());

    return isSameRepoItem || isSameSeparatorItem;
  }

  @Override
  public boolean areContentsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    return oldItem.equals(newItem);
  }
}

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

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

` cachedIn() ` অপারেশনটি এর আগে সংঘটিত যেকোনো রূপান্তরের ফলাফল ক্যাশ করে রাখে। তাই, আপনার ViewModel-এ cachedIn() -এর কলটি সবার শেষে করা উচিত।

জাভা

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
PagingRx.cachedIn(
  // Type is Flowable<PagingData<User>>.
  PagingRx.getFlowable(pager)
    .map(pagingData -> pagingData
      .filter(user -> !user.isHiddenFromUi())
      .map(UiModel.UserModel::new)),
  viewModelScope);
}

জাভা

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
PagingLiveData.cachedIn(
  Transformations.map(
    // Type is LiveData<PagingData<User>>.
    PagingLiveData.getLiveData(pager),
    pagingData -> pagingData
      .filter(user -> !user.isHiddenFromUi())
      .map(UiModel.UserModel::new)),
  viewModelScope);

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

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

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

কোডল্যাবস

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