পৃষ্ঠাযুক্ত ডেটা লোড এবং প্রদর্শন করুন

পেজিং লাইব্রেরি একটি বড় ডেটাসেট থেকে পৃষ্ঠাযুক্ত ডেটা লোড এবং প্রদর্শনের জন্য শক্তিশালী ক্ষমতা প্রদান করে। এই নির্দেশিকাটি দেখায় কিভাবে একটি নেটওয়ার্ক ডেটা উৎস থেকে পেজড ডেটার একটি স্ট্রীম সেট আপ করতে পেজিং লাইব্রেরি ব্যবহার করতে হয় এবং এটি একটি RecyclerView এ প্রদর্শন করে।

একটি ডেটা উৎস সংজ্ঞায়িত করুন

প্রথম ধাপ হল ডেটা উৎস সনাক্ত করতে একটি PagingSource বাস্তবায়ন সংজ্ঞায়িত করা। PagingSource এপিআই ক্লাসে load() পদ্ধতি অন্তর্ভুক্ত থাকে, যেটি আপনি কীভাবে সংশ্লিষ্ট ডেটা উৎস থেকে পেজড ডেটা পুনরুদ্ধার করবেন তা নির্দেশ করতে ওভাররাইড করেন।

অ্যাসিঙ্ক লোডিংয়ের জন্য Kotlin coroutines ব্যবহার করতে সরাসরি PagingSource ক্লাস ব্যবহার করুন। পেজিং লাইব্রেরি অন্যান্য অ্যাসিঙ্ক ফ্রেমওয়ার্ক সমর্থন করার জন্য ক্লাসও প্রদান করে:

  • RxJava ব্যবহার করতে, পরিবর্তে RxPagingSource প্রয়োগ করুন।
  • Guava থেকে ListenableFuture ব্যবহার করতে, পরিবর্তে ListenableFuturePagingSource প্রয়োগ করুন।

কী এবং মান প্রকার নির্বাচন করুন

PagingSource<Key, Value> এর দুটি ধরনের প্যারামিটার রয়েছে: Key এবং Value । কী ডেটা লোড করার জন্য ব্যবহৃত শনাক্তকারীকে সংজ্ঞায়িত করে এবং মান হল ডেটার প্রকার। উদাহরণস্বরূপ, যদি আপনি Retrofit-Int পৃষ্ঠা নম্বর পাস করে নেটওয়ার্ক থেকে User অবজেক্টের পৃষ্ঠাগুলি লোড করেন, তাহলে Key টাইপ হিসাবে Int এবং Value প্রকার হিসাবে User নির্বাচন করুন।

পেজিং সোর্স সংজ্ঞায়িত করুন

নিম্নলিখিত উদাহরণটি একটি 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> {
    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 = response.nextPageNumber
      )
    } 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)
    }
  }
}

জাভা

class ExamplePagingSource extends RxPagingSource<Integer, User> {
  @NonNull
  private ExampleBackendService mBackend;
  @NonNull
  private String mQuery;

  ExamplePagingSource(@NonNull ExampleBackendService backend,
    @NonNull String query) {
    mBackend = backend;
    mQuery = query;
  }

  @NotNull
  @Override
  public Single<LoadResult<Integer, User>> loadSingle(
    @NotNull LoadParams<Integer> params) {
    // Start refresh at page 1 if undefined.
    Integer nextPageNumber = params.getKey();
    if (nextPageNumber == null) {
      nextPageNumber = 1;
    }

    return mBackend.searchUsers(mQuery, nextPageNumber)
      .subscribeOn(Schedulers.io())
      .map(this::toLoadResult)
      .onErrorReturn(LoadResult.Error::new);
  }

  private LoadResult<Integer, User> toLoadResult(
    @NonNull SearchUserResponse response) {
    return new LoadResult.Page<>(
      response.getUsers(),
      null, // Only paging forward.
      response.getNextPageNumber(),
      LoadResult.Page.COUNT_UNDEFINED,
      LoadResult.Page.COUNT_UNDEFINED);
  }

  @Nullable
  @Override
  public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) {
    // 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.
    Integer anchorPosition = state.getAnchorPosition();
    if (anchorPosition == null) {
      return null;
    }

    LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
    if (anchorPage == null) {
      return null;
    }

    Integer prevKey = anchorPage.getPrevKey();
    if (prevKey != null) {
      return prevKey + 1;
    }

    Integer nextKey = anchorPage.getNextKey();
    if (nextKey != null) {
      return nextKey - 1;
    }

    return null;
  }
}

জাভা

class ExamplePagingSource extends ListenableFuturePagingSource<Integer, User> {
  @NonNull
  private ExampleBackendService mBackend;
  @NonNull
  private String mQuery;
  @NonNull
  private Executor mBgExecutor;

  ExamplePagingSource(
    @NonNull ExampleBackendService backend,
    @NonNull String query, @NonNull Executor bgExecutor) {
    mBackend = backend;
    mQuery = query;
    mBgExecutor = bgExecutor;
  }

  @NotNull
  @Override
  public ListenableFuture<LoadResult<Integer, User>> loadFuture(@NotNull LoadParams<Integer> params) {
    // Start refresh at page 1 if undefined.
    Integer nextPageNumber = params.getKey();
    if (nextPageNumber == null) {
      nextPageNumber = 1;
    }

    ListenableFuture<LoadResult<Integer, User>> pageFuture =
      Futures.transform(mBackend.searchUsers(mQuery, nextPageNumber),
      this::toLoadResult, mBgExecutor);

    ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture =
      Futures.catching(pageFuture, HttpException.class,
      LoadResult.Error::new, mBgExecutor);

    return Futures.catching(partialLoadResultFuture,
      IOException.class, LoadResult.Error::new, mBgExecutor);
  }

  private LoadResult<Integer, User> toLoadResult(@NonNull SearchUserResponse response) {
    return new LoadResult.Page<>(response.getUsers(),
    null, // Only paging forward.
    response.getNextPageNumber(),
    LoadResult.Page.COUNT_UNDEFINED,
    LoadResult.Page.COUNT_UNDEFINED);
  }

  @Nullable
  @Override
  public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) {
    // 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.
    Integer anchorPosition = state.getAnchorPosition();
    if (anchorPosition == null) {
      return null;
    }

    LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
    if (anchorPage == null) {
      return null;
    }

    Integer prevKey = anchorPage.getPrevKey();
    if (prevKey != null) {
      return prevKey + 1;
    }

    Integer nextKey = anchorPage.getNextKey();
    if (nextKey != null) {
      return nextKey - 1;
    }

    return null;
  }
}

একটি সাধারণ PagingSource বাস্তবায়ন একটি প্রশ্নের জন্য উপযুক্ত ডেটা লোড করার জন্য load() পদ্ধতিতে তার কন্সট্রাক্টরে প্রদত্ত প্যারামিটারগুলি পাস করে। উপরের উদাহরণে, সেই পরামিতিগুলি হল:

  • backend : ব্যাকএন্ড পরিষেবার একটি উদাহরণ যা ডেটা সরবরাহ করে
  • query : backend দ্বারা নির্দেশিত পরিষেবাতে পাঠানোর জন্য অনুসন্ধান ক্যোয়ারী

LoadParams অবজেক্টে লোড অপারেশন সম্পাদিত হওয়ার তথ্য রয়েছে। এর মধ্যে লোড করার কী এবং লোড করা আইটেমের সংখ্যা অন্তর্ভুক্ত।

LoadResult অবজেক্টে লোড অপারেশনের ফলাফল থাকে। LoadResult হল একটি সিল করা ক্লাস যা load() কল সফল হয়েছে কিনা তার উপর নির্ভর করে দুটি ফর্মের একটি নেয়:

  • লোড সফল হলে, একটি LoadResult.Page অবজেক্ট ফেরত দিন।
  • লোড সফল না হলে, একটি LoadResult.Error অবজেক্ট ফেরত দিন।

নিম্নলিখিত চিত্রটি ব্যাখ্যা করে যে এই উদাহরণে load() ফাংশন প্রতিটি লোডের জন্য কী গ্রহণ করে এবং পরবর্তী লোডের জন্য কী প্রদান করে।

প্রতিটি লোড() কলে, ExamplePagingSource বর্তমান কীটি নেয় এবং লোড করার জন্য পরবর্তী কীটি ফেরত দেয়।
চিত্র 1. load() কীটি কীভাবে ব্যবহার এবং আপডেট করে তা দেখানো চিত্র।

PagingSource বাস্তবায়নকে অবশ্যই একটি getRefreshKey() পদ্ধতি প্রয়োগ করতে হবে যা একটি PagingState অবজেক্টকে একটি প্যারামিটার হিসেবে নেয়। প্রাথমিক লোডের পরে ডেটা রিফ্রেশ বা অবৈধ হয়ে গেলে এটি load() পদ্ধতিতে পাস করার কী ফেরত দেয়। পেজিং লাইব্রেরি ডেটার পরবর্তী রিফ্রেশের সময় এই পদ্ধতিটিকে স্বয়ংক্রিয়ভাবে কল করে।

ত্রুটিগুলি পরিচালনা করুন

ডেটা লোড করার অনুরোধগুলি বিভিন্ন কারণে ব্যর্থ হতে পারে, বিশেষ করে যখন একটি নেটওয়ার্কে লোড হয়। load() পদ্ধতি থেকে একটি LoadResult.Error অবজেক্ট ফেরত দিয়ে লোড করার সময় যে ত্রুটির সম্মুখীন হয়েছে তার প্রতিবেদন করুন।

উদাহরণস্বরূপ, আপনি 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)
}

জাভা

return backend.searchUsers(searchTerm, nextPageNumber)
  .subscribeOn(Schedulers.io())
  .map(this::toLoadResult)
  .onErrorReturn(LoadResult.Error::new);

জাভা

ListenableFuture<LoadResult<Integer, User>> pageFuture = Futures.transform(
  backend.searchUsers(query, nextPageNumber), this::toLoadResult,
  bgExecutor);

ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture = Futures.catching(
  pageFuture, HttpException.class, LoadResult.Error::new,
  bgExecutor);

return Futures.catching(partialLoadResultFuture,
  IOException.class, LoadResult.Error::new, bgExecutor);

রেট্রোফিট ত্রুটিগুলি পরিচালনা করার বিষয়ে আরও তথ্যের জন্য, PagingSource এপিআই রেফারেন্সে নমুনাগুলি দেখুন৷

PagingSource UI-তে LoadResult.Error অবজেক্ট সংগ্রহ করে এবং বিতরণ করে যাতে আপনি তাদের উপর কাজ করতে পারেন। UI-তে লোডিং অবস্থা প্রকাশ করার বিষয়ে আরও তথ্যের জন্য, লোডিং অবস্থা পরিচালনা করুন এবং উপস্থিত করুন দেখুন।

পেজিং ডেটার একটি স্ট্রিম সেট আপ করুন

এর পরে, আপনার PagingSource বাস্তবায়ন থেকে পৃষ্ঠাযুক্ত ডেটার একটি স্ট্রিম প্রয়োজন। আপনার ViewModel এ ডেটা স্ট্রিম সেট আপ করুন। Pager ক্লাস এমন পদ্ধতি প্রদান করে যা একটি PagingSource থেকে PagingData অবজেক্টের প্রতিক্রিয়াশীল স্ট্রিম প্রকাশ করে। পেজিং লাইব্রেরি RxJava থেকে Flow , LiveData , এবং Flowable এবং Observable প্রকারগুলি সহ বেশ কয়েকটি স্ট্রিম প্রকার ব্যবহার করে সমর্থন করে৷

যখন আপনি আপনার প্রতিক্রিয়াশীল স্ট্রীম সেট আপ করার জন্য একটি Pager ইনস্ট্যান্স তৈরি করেন, তখন আপনাকে অবশ্যই একটি PagingConfig কনফিগারেশন অবজেক্ট এবং একটি ফাংশন সহ দৃষ্টান্ত প্রদান করতে হবে যা Pager আপনার PagingSource বাস্তবায়নের একটি উদাহরণ কীভাবে পেতে হয় তা বলে:

কোটলিন

val flow = Pager(
  // Configure how data is loaded by passing additional properties to
  // PagingConfig, such as prefetchDistance.
  PagingConfig(pageSize = 20)
) {
  ExamplePagingSource(backend, query)
}.flow
  .cachedIn(viewModelScope)

জাভা

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
  new PagingConfig(/* pageSize = */ 20),
  () -> ExamplePagingSource(backend, query));

Flowable<PagingData<User>> flowable = PagingRx.getFlowable(pager);
PagingRx.cachedIn(flowable, viewModelScope);

জাভা

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
  new PagingConfig(/* pageSize = */ 20),
  () -> ExamplePagingSource(backend, query));

PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);

cachedIn() অপারেটর ডেটা স্ট্রীমকে শেয়ার করার যোগ্য করে তোলে এবং প্রদত্ত CoroutineScope দিয়ে লোড করা ডেটা ক্যাশ করে। এই উদাহরণটি লাইফসাইকেল lifecycle-viewmodel-ktx আর্টিফ্যাক্ট দ্বারা প্রদত্ত viewModelScope ব্যবহার করে।

Pager অবজেক্টটি PagingSource অবজেক্ট থেকে load() পদ্ধতিকে কল করে, এটি LoadParams অবজেক্টের সাথে প্রদান করে এবং বিনিময়ে LoadResult অবজেক্ট গ্রহণ করে।

একটি RecyclerView অ্যাডাপ্টার সংজ্ঞায়িত করুন

আপনার RecyclerView তালিকায় ডেটা গ্রহণ করার জন্য আপনাকে একটি অ্যাডাপ্টার সেট আপ করতে হবে। পেজিং লাইব্রেরি এই উদ্দেশ্যে PagingDataAdapter ক্লাস প্রদান করে।

একটি ক্লাস সংজ্ঞায়িত করুন যা PagingDataAdapter প্রসারিত করে। উদাহরণে, UserAdapter User ধরনের তালিকা আইটেমগুলির জন্য একটি RecyclerView অ্যাডাপ্টার প্রদান করতে এবং একটি ভিউ হোল্ডার হিসাবে UserViewHolder ব্যবহার করার জন্য PagingDataAdapter প্রসারিত করে:

কোটলিন

class UserAdapter(diffCallback: DiffUtil.ItemCallback<User>) :
  PagingDataAdapter<User, UserViewHolder>(diffCallback) {
  override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int
  ): UserViewHolder {
    return UserViewHolder(parent)
  }

  override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
    val item = getItem(position)
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item)
  }
}

জাভা

class UserAdapter extends PagingDataAdapter<User, UserViewHolder> {
  UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) {
    super(diffCallback);
  }

  @NonNull
  @Override
  public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new UserViewHolder(parent);
  }

  @Override
  public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
    User item = getItem(position);
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item);
  }
}

জাভা

class UserAdapter extends PagingDataAdapter<User, UserViewHolder> {
  UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) {
    super(diffCallback);
  }

  @NonNull
  @Override
  public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new UserViewHolder(parent);
  }

  @Override
  public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
    User item = getItem(position);
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item);
  }
}

আপনার অ্যাডাপ্টারকে অবশ্যই onCreateViewHolder() এবং onBindViewHolder() পদ্ধতিগুলিকে সংজ্ঞায়িত করতে হবে এবং একটি DiffUtil.ItemCallback নির্দিষ্ট করতে হবে। RecyclerView তালিকা অ্যাডাপ্টারগুলি সংজ্ঞায়িত করার সময় এটি সাধারণত যেমন কাজ করে:

কোটলিন

object UserComparator : DiffUtil.ItemCallback<User>() {
  override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
    // Id is unique.
    return oldItem.id == newItem.id
  }

  override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
    return oldItem == newItem
  }
}

জাভা

class UserComparator extends DiffUtil.ItemCallback<User> {
  @Override
  public boolean areItemsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    // Id is unique.
    return oldItem.id.equals(newItem.id);
  }

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

জাভা

class UserComparator extends DiffUtil.ItemCallback<User> {
  @Override
  public boolean areItemsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    // Id is unique.
    return oldItem.id.equals(newItem.id);
  }

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

আপনার UI এ পৃষ্ঠাযুক্ত ডেটা প্রদর্শন করুন

এখন যেহেতু আপনি একটি PagingSource সংজ্ঞায়িত করেছেন, আপনার অ্যাপের জন্য একটি PagingData স্ট্রিম তৈরি করার একটি উপায় তৈরি করেছেন এবং একটি PagingDataAdapter সংজ্ঞায়িত করেছেন, আপনি এই উপাদানগুলিকে একসাথে সংযুক্ত করতে এবং আপনার কার্যকলাপে পৃষ্ঠাযুক্ত ডেটা প্রদর্শন করতে প্রস্তুত৷

আপনার কার্যকলাপের onCreate বা খণ্ডের onViewCreated পদ্ধতিতে নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করুন:

  1. আপনার PagingDataAdapter ক্লাসের একটি উদাহরণ তৈরি করুন।
  2. আপনি আপনার পৃষ্ঠাযুক্ত ডেটা প্রদর্শন করতে চান এমন RecyclerView তালিকায় PagingDataAdapter উদাহরণটি পাস করুন।
  3. PagingData স্ট্রীমটি পর্যবেক্ষণ করুন এবং আপনার অ্যাডাপ্টারের submitData() পদ্ধতিতে প্রতিটি জেনারেট করা মান পাস করুন।

কোটলিন

val viewModel by viewModels<ExampleViewModel>()

val pagingAdapter = UserAdapter(UserComparator)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = pagingAdapter

// Activities can use lifecycleScope directly; fragments use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  viewModel.flow.collectLatest { pagingData ->
    pagingAdapter.submitData(pagingData)
  }
}

জাভা

ExampleViewModel viewModel = new ViewModelProvider(this)
  .get(ExampleViewModel.class);

UserAdapter pagingAdapter = new UserAdapter(new UserComparator());
RecyclerView recyclerView = findViewById<RecyclerView>(
  R.id.recycler_view);
recyclerView.adapter = pagingAdapter

viewModel.flowable
  // Using AutoDispose to handle subscription lifecycle.
  // See: https://github.com/uber/AutoDispose.
  .to(autoDisposable(AndroidLifecycleScopeProvider.from(this)))
  .subscribe(pagingData -> pagingAdapter.submitData(lifecycle, pagingData));

জাভা

ExampleViewModel viewModel = new ViewModelProvider(this)
  .get(ExampleViewModel.class);

UserAdapter pagingAdapter = new UserAdapter(new UserComparator());
RecyclerView recyclerView = findViewById<RecyclerView>(
  R.id.recycler_view);
recyclerView.adapter = pagingAdapter

// Activities can use getLifecycle() directly; fragments use
// getViewLifecycleOwner().getLifecycle().
viewModel.liveData.observe(this, pagingData ->
  pagingAdapter.submitData(getLifecycle(), pagingData));

RecyclerView তালিকা এখন ডেটা উৎস থেকে পৃষ্ঠাযুক্ত ডেটা প্রদর্শন করে এবং প্রয়োজনে স্বয়ংক্রিয়ভাবে অন্য পৃষ্ঠা লোড করে।

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

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

কোডল্যাব

নমুনা

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}