ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন
পেজিং লাইব্রেরি পেজ করা ডেটার লোড রিকোয়েস্টের অবস্থা ট্র্যাক করে এবং LoadState ক্লাসের মাধ্যমে তা প্রকাশ করে। আপনার অ্যাপ বর্তমান অবস্থা সম্পর্কে তথ্য পেতে এবং সেই অনুযায়ী UI আপডেট করতে PagingDataAdapter সাথে একটি লিসেনার রেজিস্টার করতে পারে। এই স্টেটগুলো অ্যাডাপ্টার থেকে সরবরাহ করা হয়, কারণ এগুলো UI-এর সাথে সিনক্রোনাস। এর মানে হলো, যখন UI-তে পেজ লোড প্রয়োগ করা হয়, তখনই আপনার লিসেনার আপডেটগুলো পায়।
প্রতিটি LoadType এবং ডেটা সোর্স টাইপের ( PagingSource অথবা RemoteMediator ) জন্য একটি পৃথক LoadState সিগন্যাল প্রদান করা হয়। লিসেনার দ্বারা প্রদত্ত CombinedLoadStates অবজেক্টটি এই সমস্ত সিগন্যাল থেকে লোডিং অবস্থা সম্পর্কে তথ্য সরবরাহ করে। আপনি এই বিস্তারিত তথ্য ব্যবহার করে আপনার ব্যবহারকারীদের কাছে উপযুক্ত লোডিং ইন্ডিকেটরগুলো প্রদর্শন করতে পারেন।
লোড হওয়ার অবস্থা
পেজিং লাইব্রেরি LoadState অবজেক্টের মাধ্যমে UI-তে ব্যবহারের জন্য লোডিং অবস্থা প্রকাশ করে। বর্তমান লোডিং অবস্থার উপর নির্ভর করে LoadState অবজেক্টগুলো তিনটি রূপের মধ্যে যেকোনো একটি গ্রহণ করে:
- যদি কোনো সক্রিয় লোড অপারেশন না থাকে এবং কোনো ত্রুটিও না থাকে, তাহলে
LoadStateএকটিLoadState.NotLoadingঅবজেক্ট হয়। এই সাবক্লাসটিতেendOfPaginationReachedপ্রপার্টিটিও অন্তর্ভুক্ত রয়েছে, যা নির্দেশ করে যে পেজিনেশনের শেষ প্রান্তে পৌঁছানো হয়েছে কিনা। - যদি কোনো সক্রিয় লোড অপারেশন থাকে, তাহলে
LoadStateহলো একটিLoadState.Loadingঅবজেক্ট। - যদি কোনো ত্রুটি থাকে, তাহলে
LoadStateএকটিLoadState.Errorঅবজেক্ট হবে।
আপনার UI-তে LoadState ব্যবহার করার দুটি উপায় আছে: একটি লিসেনার ব্যবহার করে, অথবা একটি বিশেষ লিস্ট অ্যাডাপ্টার ব্যবহার করে সরাসরি RecyclerView লিস্টে লোডিং স্টেটটি প্রদর্শন করা।
একটি লিসেনারের মাধ্যমে লোডিং অবস্থা অ্যাক্সেস করুন
আপনার UI-তে সাধারণ ব্যবহারের জন্য লোডিং স্টেট পেতে, আপনার PagingDataAdapter দ্বারা প্রদত্ত loadStateFlow স্ট্রিম অথবা addLoadStateListener() মেথডটি ব্যবহার করুন। এই পদ্ধতিগুলো একটি CombinedLoadStates অবজেক্টে অ্যাক্সেস দেয়, যেখানে প্রতিটি লোড টাইপের LoadState আচরণ সম্পর্কিত তথ্য অন্তর্ভুক্ত থাকে।
নিম্নলিখিত উদাহরণে, PagingDataAdapter রিফ্রেশ লোডের বর্তমান অবস্থার উপর নির্ভর করে বিভিন্ন UI উপাদান প্রদর্শন করে:
কোটলিন
// Activities can use lifecycleScope directly, but Fragments should instead use // viewLifecycleOwner.lifecycleScope. lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> progressBar.isVisible = loadStates.refresh is LoadState.Loading retry.isVisible = loadState.refresh !is LoadState.Loading errorMsg.isVisible = loadState.refresh is LoadState.Error } }
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
CombinedLoadStates সম্পর্কে আরও তথ্যের জন্য, অতিরিক্ত লোডিং অবস্থার তথ্য অ্যাক্সেস করুন দেখুন।
একটি অ্যাডাপ্টারের সাহায্যে লোডিং অবস্থা উপস্থাপন করুন।
পেজিং লাইব্রেরিটি পেজ করা ডেটার প্রদর্শিত তালিকায় সরাসরি লোডিং অবস্থা উপস্থাপনের উদ্দেশ্যে LoadStateAdapter নামক আরেকটি লিস্ট অ্যাডাপ্টার প্রদান করে। এই অ্যাডাপ্টারটি তালিকার বর্তমান লোড অবস্থায় অ্যাক্সেস দেয়, যা আপনি তথ্য প্রদর্শনকারী একটি কাস্টম ভিউ হোল্ডারে পাস করতে পারেন।
প্রথমে, একটি ভিউ হোল্ডার ক্লাস তৈরি করুন যা আপনার স্ক্রিনের লোডিং এবং এরর ভিউগুলোর রেফারেন্স রাখবে। একটি bind() ফাংশন তৈরি করুন যা প্যারামিটার হিসেবে একটি LoadState গ্রহণ করবে। এই ফাংশনটি লোড স্টেট প্যারামিটারের উপর ভিত্তি করে ভিউয়ের ভিজিবিলিটি টগল করবে:
কোটলিন
class LoadStateViewHolder( parent: ViewGroup, retry: () -> Unit ) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.load_state_item, parent, false) ) { private val binding = LoadStateItemBinding.bind(itemView) private val progressBar: ProgressBar = binding.progressBar private val errorMsg: TextView = binding.errorMsg private val retry: Button = binding.retryButton .also { it.setOnClickListener { retry() } } fun bind(loadState: LoadState) { if (loadState is LoadState.Error) { errorMsg.text = loadState.error.localizedMessage } progressBar.isVisible = loadState is LoadState.Loading retry.isVisible = loadState is LoadState.Error errorMsg.isVisible = loadState is LoadState.Error } }
জাভা
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
জাভা
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
এরপরে, LoadStateAdapter ইমপ্লিমেন্ট করে এমন একটি ক্লাস তৈরি করুন এবং onCreateViewHolder() ও onBindViewHolder() মেথডগুলো সংজ্ঞায়িত করুন। এই মেথডগুলো আপনার কাস্টম ভিউ হোল্ডারের একটি ইনস্ট্যান্স তৈরি করে এবং সংশ্লিষ্ট লোড স্টেটকে বাইন্ড করে।
কোটলিন
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter( private val retry: () -> Unit ) : LoadStateAdapter<LoadStateViewHolder>() { override fun onCreateViewHolder( parent: ViewGroup, loadState: LoadState ) = LoadStateViewHolder(parent, retry) override fun onBindViewHolder( holder: LoadStateViewHolder, loadState: LoadState ) = holder.bind(loadState) }
জাভা
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
জাভা
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
লোডিং অবস্থাটি হেডার বা ফুটার হিসেবে প্রদর্শন করুন
হেডার এবং ফুটারে লোডিং অগ্রগতি প্রদর্শন করতে, আপনার PagingDataAdapter অবজেক্ট থেকে withLoadStateHeaderAndFooter() মেথডটি কল করুন:
কোটলিন
pagingAdapter .withLoadStateHeaderAndFooter( header = ExampleLoadStateAdapter(adapter::retry), footer = ExampleLoadStateAdapter(adapter::retry) )
জাভা
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
জাভা
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
আপনি যদি চান যে RecyclerView লিস্টটি লোডিং অবস্থা শুধু হেডারে অথবা শুধু ফুটারে প্রদর্শন করুক, তাহলে এর পরিবর্তে withLoadStateHeader() বা withLoadStateFooter() কল করতে পারেন।
অতিরিক্ত লোডিং অবস্থার তথ্য অ্যাক্সেস করুন
PagingDataAdapter এর CombinedLoadStates অবজেক্টটি আপনার PagingSource ইমপ্লিমেন্টেশন এবং আপনার RemoteMediator ইমপ্লিমেন্টেশনের (যদি থাকে) লোড স্টেট সম্পর্কিত তথ্য প্রদান করে।
সুবিধার জন্য, আপনি উপযুক্ত লোড টাইপের একটি LoadState অবজেক্ট অ্যাক্সেস করতে CombinedLoadStates থেকে refresh , append , এবং prepend প্রোপার্টিগুলো ব্যবহার করতে পারেন। এই প্রোপার্টিগুলো সাধারণত RemoteMediator ইমপ্লিমেন্টেশনের লোড স্টেটকে প্রাধান্য দেয়, যদি সেরকম কোনো লোড স্টেট বিদ্যমান থাকে; অন্যথায়, এগুলোতে PagingSource ইমপ্লিমেন্টেশন থেকে উপযুক্ত লোড স্টেট থাকে। এর পেছনের লজিক সম্পর্কে আরও বিস্তারিত তথ্যের জন্য, CombinedLoadStates এর রেফারেন্স ডকুমেন্টেশন দেখুন।
কোটলিন
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. refreshLoadState: LoadState = loadStates.refresh // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. prependLoadState: LoadState = loadStates.prepend // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. appendLoadState: LoadState = loadStates.append } }
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
তবে, এটা মনে রাখা গুরুত্বপূর্ণ যে শুধুমাত্র PagingSource লোড স্টেটগুলোই UI আপডেটের সাথে সিনক্রোনাস হওয়ার নিশ্চয়তা দেয়। যেহেতু refresh , append , এবং prepend প্রোপার্টিগুলো PagingSource বা RemoteMediator যেকোনোটি থেকেই লোড স্টেট নিতে পারে, তাই এগুলো UI আপডেটের সাথে সিনক্রোনাস হওয়ার নিশ্চয়তা দেয় না। এর ফলে UI-তে এমন সমস্যা দেখা দিতে পারে, যেখানে UI-তে কোনো নতুন ডেটা যুক্ত হওয়ার আগেই লোডিং শেষ হয়ে গেছে বলে মনে হয়।
এই কারণে, হেডার বা ফুটারে লোড স্টেট দেখানোর জন্য কনভেনিয়েন্স অ্যাক্সেসরগুলো ভালোভাবে কাজ করে, কিন্তু অন্যান্য ব্যবহারের ক্ষেত্রে আপনার PagingSource অথবা RemoteMediator থেকে নির্দিষ্টভাবে লোড স্টেট অ্যাক্সেস করার প্রয়োজন হতে পারে। এই উদ্দেশ্যে CombinedLoadStates source এবং mediator প্রোপার্টিগুলো প্রদান করে। এই প্রোপার্টিগুলোর প্রতিটি একটি LoadStates অবজেক্ট প্রকাশ করে, যার মধ্যে যথাক্রমে PagingSource বা RemoteMediator জন্য LoadState অবজেক্টগুলো থাকে:
কোটলিন
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Directly access the RemoteMediator refresh load state. mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh // Directly access the RemoteMediator append load state. mediatorAppendLoadState: LoadState? = loadStates.mediator.append // Directly access the RemoteMediator prepend load state. mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend // Directly access the PagingSource refresh load state. sourceRefreshLoadState: LoadState = loadStates.source.refresh // Directly access the PagingSource append load state. sourceAppendLoadState: LoadState = loadStates.source.append // Directly access the PagingSource prepend load state. sourcePrependLoadState: LoadState = loadStates.source.prepend } }
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
জাভা
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
LoadState-এ চেইন অপারেটর
যেহেতু CombinedLoadStates অবজেক্টটি লোড স্টেটের সমস্ত পরিবর্তনে অ্যাক্সেস দেয়, তাই নির্দিষ্ট ইভেন্টের উপর ভিত্তি করে লোড স্টেট স্ট্রিম ফিল্টার করা গুরুত্বপূর্ণ। এটি নিশ্চিত করে যে আপনি সঠিক সময়ে আপনার UI আপডেট করছেন, যার ফলে স্টাটার এবং অপ্রয়োজনীয় UI আপডেট এড়ানো যায়।
উদাহরণস্বরূপ, ধরুন আপনি একটি খালি ভিউ প্রদর্শন করতে চান, কিন্তু শুধুমাত্র প্রাথমিক ডেটা লোড সম্পন্ন হওয়ার পরেই। এই ব্যবহারের ক্ষেত্রে, আপনাকে যাচাই করতে হবে যে একটি ডেটা রিফ্রেশ লোড শুরু হয়েছে, এবং তারপর রিফ্রেশ সম্পন্ন হয়েছে তা নিশ্চিত করার জন্য NotLoading অবস্থাটির জন্য অপেক্ষা করতে হবে। আপনার প্রয়োজনীয় সিগন্যালগুলো ছাড়া বাকি সব সিগন্যাল ফিল্টার করে বাদ দিতে হবে:
কোটলিন
lifecycleScope.launchWhenCreated { adapter.loadStateFlow // Only emit when REFRESH LoadState for RemoteMediator changes. .distinctUntilChangedBy { it.refresh } // Only react to cases where REFRESH completes, such as NotLoading. .filter { it.refresh is LoadState.NotLoading } // Scroll to top is synchronous with UI updates, even if remote load was // triggered. .collect { binding.list.scrollToPosition(0) } }
জাভা
PublishSubject<CombinedLoadStates> subject = PublishSubject.create(); Disposable disposable = subject.distinctUntilChanged(CombinedLoadStates::getRefresh) .filter( combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading) .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0)); pagingAdapter.addLoadStateListener(loadStates -> { subject.onNext(loadStates); });
জাভা
LiveData<CombinedLoadStates> liveData = new MutableLiveData<>(); LiveData<LoadState> refreshLiveData = Transformations.map(liveData, CombinedLoadStates::getRefresh); LiveData<LoadState> distinctLiveData = Transformations.distinctUntilChanged(refreshLiveData); distinctLiveData.observeForever(loadState -> { if (loadState instanceof LoadState.NotLoading) { binding.list.scrollToPosition(0); } });
এই উদাহরণটি রিফ্রেশ লোড স্টেট আপডেট হওয়া পর্যন্ত অপেক্ষা করে, কিন্তু শুধুমাত্র তখনই ট্রিগার হয় যখন স্টেটটি NotLoading থাকে। এটি নিশ্চিত করে যে, যেকোনো UI আপডেট হওয়ার আগে রিমোট রিফ্রেশ সম্পূর্ণরূপে শেষ হয়েছে।
স্ট্রিম এপিআই এই ধরনের কার্যক্রম সম্ভব করে তোলে। আপনার অ্যাপ তার প্রয়োজনীয় লোড ইভেন্টগুলো নির্দিষ্ট করতে পারে এবং উপযুক্ত শর্ত পূরণ হলে নতুন ডেটা পরিচালনা করতে পারে।
{% হুবহু %}আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলেও লিঙ্কের লেখা প্রদর্শিত হয়।
- পৃষ্ঠা ডেটা লোড এবং প্রদর্শন করুন
- নেটওয়ার্ক এবং ডেটাবেস থেকে পৃষ্ঠা
- পেজিং লাইব্রেরির সংক্ষিপ্ত বিবরণ