ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন
নেটওয়ার্ক সংযোগ অনির্ভরযোগ্য হলে বা ব্যবহারকারী অফলাইনে থাকলেও যেন আপনার অ্যাপটি ব্যবহার করা যায়, তা নিশ্চিত করে ব্যবহারকারীর অভিজ্ঞতা উন্নত করুন। এটি করার একটি উপায় হলো একই সাথে নেটওয়ার্ক এবং লোকাল ডেটাবেস থেকে পেজ করা। এইভাবে, আপনার অ্যাপটি লোকাল ডেটাবেস ক্যাশে থেকে ইউজার ইন্টারফেস (UI) পরিচালনা করে এবং ডেটাবেসে আর কোনো ডেটা না থাকলেই কেবল নেটওয়ার্কে অনুরোধ পাঠায়।
এই নির্দেশিকাটি ধরে নেয় যে আপনি Room persistence লাইব্রেরি এবং Paging লাইব্রেরির প্রাথমিক ব্যবহার সম্পর্কে পরিচিত।
সমন্বয় ডেটা লোড
পেজিং লাইব্রেরি এই ব্যবহারের জন্য RemoteMediator কম্পোনেন্টটি প্রদান করে। অ্যাপের ক্যাশ করা ডেটা শেষ হয়ে গেলে, পেজিং লাইব্রেরি থেকে RemoteMediator একটি সিগন্যাল হিসেবে কাজ করে। আপনি এই সিগন্যালটি ব্যবহার করে নেটওয়ার্ক থেকে অতিরিক্ত ডেটা লোড করে লোকাল ডেটাবেসে সংরক্ষণ করতে পারেন, যেখান থেকে একটি PagingSource তা লোড করে প্রদর্শনের জন্য UI-তে সরবরাহ করতে পারে।
যখন অতিরিক্ত ডেটার প্রয়োজন হয়, তখন পেজিং লাইব্রেরি RemoteMediator ইমপ্লিমেন্টেশন থেকে load() মেথডটিকে কল করে। এটি একটি সাসপেন্ডিং ফাংশন, তাই এখানে দীর্ঘ সময় ধরে চলা কাজ নিরাপদে করা যায়। এই ফাংশনটি সাধারণত কোনো নেটওয়ার্ক সোর্স থেকে নতুন ডেটা সংগ্রহ করে এবং সেটিকে লোকাল স্টোরেজে সংরক্ষণ করে।
এই প্রক্রিয়াটি নতুন ডেটা নিয়ে কাজ করে, কিন্তু সময়ের সাথে সাথে ডাটাবেসে সংরক্ষিত ডেটার বৈধতা বাতিল করার প্রয়োজন হয়, যেমন যখন ব্যবহারকারী নিজে থেকে রিফ্রেশ চালু করেন। এটি load() মেথডে পাস করা LoadType প্রপার্টির মাধ্যমে প্রকাশ করা হয়। ` LoadType RemoteMediator কে জানিয়ে দেয় যে, তাকে বিদ্যমান ডেটা রিফ্রেশ করতে হবে, নাকি বিদ্যমান তালিকার শুরুতে বা শেষে যোগ করার জন্য অতিরিক্ত ডেটা আনতে হবে।
এইভাবে, RemoteMediator নিশ্চিত করে যে আপনার অ্যাপ ব্যবহারকারীরা যা দেখতে চান সেই ডেটা সঠিক ক্রমে লোড করে।
পেজিং জীবনচক্র

নেটওয়ার্ক থেকে সরাসরি পেজিং করার সময়, PagingSource ডেটা লোড করে এবং একটি LoadResult অবজেক্ট রিটার্ন করে। PagingSource ইমপ্লিমেন্টেশনটি pagingSourceFactory প্যারামিটারের মাধ্যমে Pager এ পাস করা হয়।
UI-এর জন্য নতুন ডেটার প্রয়োজন হলে, Pager PagingSource থেকে load() মেথডটি কল করে এবং নতুন ডেটা ধারণকারী PagingData অবজেক্টের একটি স্ট্রিম রিটার্ন করে। প্রতিটি PagingData অবজেক্ট সাধারণত UI-তে প্রদর্শনের জন্য পাঠানোর আগে ViewModel এ ক্যাশ করা হয়।

RemoteMediator এই ডেটা প্রবাহ পরিবর্তন করে। একটি PagingSource এখনও ডেটা লোড করে; কিন্তু যখন পেজ করা ডেটা শেষ হয়ে যায়, তখন Paging লাইব্রেরি নেটওয়ার্ক সোর্স থেকে নতুন ডেটা লোড করার জন্য RemoteMediator কে ট্রিগার করে। RemoteMediator নতুন ডেটা লোকাল ডেটাবেসে সংরক্ষণ করে, তাই ViewModel এ একটি ইন-মেমরি ক্যাশের প্রয়োজন হয় না। সবশেষে, PagingSource নিজেকে ইনভ্যালিডেট করে, এবং Pager ডেটাবেস থেকে নতুন ডেটা লোড করার জন্য একটি নতুন ইনস্ট্যান্স তৈরি করে।
মৌলিক ব্যবহার
ধরুন, আপনি চান আপনার অ্যাপটি একটি আইটেম-ভিত্তিক নেটওয়ার্ক ডেটা সোর্স থেকে User আইটেমের পেজগুলো লোড করে একটি রুম ডেটাবেসে সংরক্ষিত লোকাল ক্যাশে রাখবে।
একটি RemoteMediator ইমপ্লিমেন্টেশন নেটওয়ার্ক থেকে পেজ করা ডেটা ডাটাবেসে লোড করতে সাহায্য করে, কিন্তু সরাসরি UI-তে ডেটা লোড করে না। এর পরিবর্তে, অ্যাপটি ডাটাবেসকে তথ্যের মূল উৎস হিসেবে ব্যবহার করে। অন্য কথায়, অ্যাপটি শুধুমাত্র সেই ডেটা প্রদর্শন করে যা ডাটাবেসে ক্যাশ করা হয়েছে। একটি PagingSource ইমপ্লিমেন্টেশন (উদাহরণস্বরূপ, Room দ্বারা জেনারেট করা একটি) ডাটাবেস থেকে ক্যাশ করা ডেটা UI-তে লোড করার কাজটি পরিচালনা করে।
রুম সত্তা তৈরি করুন
প্রথম ধাপ হলো Room পার্সিস্টেন্স লাইব্রেরি ব্যবহার করে একটি ডেটাবেস সংজ্ঞায়িত করা, যা নেটওয়ার্ক ডেটা সোর্স থেকে পেজ করা ডেটার একটি স্থানীয় ক্যাশে ধারণ করবে। “Save data in a local database using Room” অংশে বর্ণিত RoomDatabase এর একটি ইমপ্লিমেন্টেশন দিয়ে শুরু করুন।
এরপরে, "রুম এনটিটি ব্যবহার করে ডেটা সংজ্ঞায়িত করা" অংশে বর্ণিত পদ্ধতি অনুসারে, তালিকার আইটেমগুলোর একটি টেবিল উপস্থাপন করার জন্য একটি রুম এনটিটি সংজ্ঞায়িত করুন। এটিকে প্রাইমারি কী হিসেবে একটি id ফিল্ড দিন, এবং সেইসাথে আপনার তালিকার আইটেমগুলোতে থাকা অন্য যেকোনো তথ্যের জন্য ফিল্ড যুক্ত করুন।
জাভা
@Entity(tableName = "users") public class User { public String id; public String label; }
জাভা
@Entity(tableName = "users") public class User { public String id; public String label; }
“রুম ডিএও ব্যবহার করে ডেটা অ্যাক্সেস করা” অংশে বর্ণিত পদ্ধতি অনুসারে আপনাকে অবশ্যই এই রুম এনটিটির জন্য একটি ডেটা অ্যাক্সেস অবজেক্ট (ডিএও) সংজ্ঞায়িত করতে হবে। লিস্ট আইটেম এনটিটির ডিএও-তে নিম্নলিখিত মেথডগুলো অবশ্যই অন্তর্ভুক্ত থাকতে হবে:
- একটি
insertAll()মেথড যা টেবিলে আইটেমের একটি তালিকা সন্নিবেশ করে। - এমন একটি মেথড যা প্যারামিটার হিসেবে কোয়েরি স্ট্রিং গ্রহণ করে এবং ফলাফলের তালিকার জন্য একটি
PagingSourceঅবজেক্ট রিটার্ন করে। এর ফলে, একটিPagerঅবজেক্ট এই টেবিলটিকে পেজ করা ডেটার উৎস হিসেবে ব্যবহার করতে পারে। - একটি
clearAll()মেথড যা টেবিলের সমস্ত ডেটা মুছে ফেলে।
জাভা
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<User> users); @Query("SELECT * FROM users WHERE mLabel LIKE :query") PagingSource<Integer, User> pagingSource(String query); @Query("DELETE FROM users") int clearAll(); }
জাভা
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<User> users); @Query("SELECT * FROM users WHERE mLabel LIKE :query") PagingSource<Integer, User> pagingSource(String query); @Query("DELETE FROM users") int clearAll(); }
একটি রিমোটমিডিয়েটর প্রয়োগ করুন
RemoteMediator এর প্রধান কাজ হলো, যখন Pager ডেটা শেষ হয়ে যায় অথবা বিদ্যমান ডেটা অবৈধ হয়ে যায়, তখন নেটওয়ার্ক থেকে আরও ডেটা লোড করা। এতে একটি load() মেথড রয়েছে, যা লোডিং আচরণ নির্ধারণ করার জন্য আপনাকে অবশ্যই ওভাররাইড করতে হবে।
একটি সাধারণ RemoteMediator বাস্তবায়নে নিম্নলিখিত প্যারামিটারগুলো অন্তর্ভুক্ত থাকে:
-
query: একটি কোয়েরি স্ট্রিং যা নির্ধারণ করে ব্যাকএন্ড পরিষেবা থেকে কোন ডেটা পুনরুদ্ধার করতে হবে। -
database: রুম ডাটাবেস যা লোকাল ক্যাশ হিসেবে কাজ করে। -
networkService: ব্যাকএন্ড সার্ভিসের জন্য একটি এপিআই ইনস্ট্যান্স।
একটি RemoteMediator<Key, Value> ইমপ্লিমেন্টেশন তৈরি করুন। Key টাইপ এবং Value টাইপ ঠিক তেমনই হওয়া উচিত, যেমনটি একই নেটওয়ার্ক ডেটা সোর্সের বিপরীতে একটি PagingSource সংজ্ঞায়িত করার ক্ষেত্রে হয়ে থাকে। টাইপ প্যারামিটার নির্বাচন সম্পর্কে আরও তথ্যের জন্য, "Key এবং Value টাইপ নির্বাচন করুন" দেখুন।
জাভা
@UseExperimental(markerClass = ExperimentalPagingApi.class) class ExampleRemoteMediator extends RxRemoteMediator<Integer, User> { private String query; private ExampleBackendService networkService; private RoomDb database; private UserDao userDao; ExampleRemoteMediator( String query, ExampleBackendService networkService, RoomDb database ) { query = query; networkService = networkService; database = database; userDao = database.userDao(); } @NotNull @Override public Single<MediatorResult> loadSingle( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { ... } }
জাভা
class ExampleRemoteMediator extends ListenableFutureRemoteMediator<Integer, User> { private String query; private ExampleBackendService networkService; private RoomDb database; private UserDao userDao; private Executor bgExecutor; ExampleRemoteMediator( String query, ExampleBackendService networkService, RoomDb database, Executor bgExecutor ) { this.query = query; this.networkService = networkService; this.database = database; this.userDao = database.userDao(); this.bgExecutor = bgExecutor; } @NotNull @Override public ListenableFuture<MediatorResult> loadFuture( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { ... } }
` load() ` মেথডটি ব্যাকেন্ড ডেটাসেট আপডেট করা এবং PagingSource অবৈধ করার জন্য দায়ী। পেজিং সমর্থনকারী কিছু লাইব্রেরি (যেমন `Room`) তাদের দ্বারা বাস্তবায়িত PagingSource অবজেক্টগুলোকে স্বয়ংক্রিয়ভাবে অবৈধ করে দেয়।
` load() ` মেথডটি দুটি প্যারামিটার গ্রহণ করে:
-
PagingState, যাতে এখন পর্যন্ত লোড হওয়া পেজগুলো, সর্বশেষ অ্যাক্সেস করা ইনডেক্স এবং পেজিং স্ট্রিম শুরু করতে ব্যবহৃতPagingConfigঅবজেক্টের তথ্য থাকে। -
LoadType, যা লোডের ধরণ নির্দেশ করে:REFRESH,APPEND, বাPREPEND।
load() ` মেথডের রিটার্ন ভ্যালু হলো একটি MediatorResult অবজেক্ট। MediatorResult হয় MediatorResult.Error (যেটিতে ত্রুটির বিবরণ থাকে) অথবা MediatorResult.Success (যেটিতে আরও ডেটা লোড করার আছে কি না, তা জানানোর জন্য একটি সংকেত থাকে)।
` load() ` মেথডটিকে অবশ্যই নিম্নলিখিত ধাপগুলো সম্পাদন করতে হবে:
- লোডের ধরন এবং এ পর্যন্ত লোড হওয়া ডেটার ওপর নির্ভর করে নেটওয়ার্ক থেকে কোন পেজটি লোড করতে হবে তা নির্ধারণ করুন।
- নেটওয়ার্ক অনুরোধটি চালু করুন।
- লোড অপারেশনের ফলাফলের উপর নির্ভর করে পদক্ষেপ গ্রহণ করুন:
- যদি লোড সফল হয় এবং প্রাপ্ত আইটেমের তালিকাটি খালি না হয়, তাহলে তালিকার আইটেমগুলো ডেটাবেসে সংরক্ষণ করুন এবং
MediatorResult.Success(endOfPaginationReached = false)রিটার্ন করুন। ডেটা সংরক্ষিত হওয়ার পরে, পেজিং লাইব্রেরিকে নতুন ডেটা সম্পর্কে অবহিত করতে ডেটা সোর্সটিকে অবৈধ করে দিন। - যদি লোড সফল হয় এবং প্রাপ্ত আইটেমের তালিকাটি খালি থাকে অথবা এটি সর্বশেষ পেজ ইনডেক্স হয়, তাহলে
MediatorResult.Success(endOfPaginationReached = true)রিটার্ন করুন। ডেটা সংরক্ষিত হওয়ার পরে, পেজিং লাইব্রেরিকে নতুন ডেটা সম্পর্কে অবহিত করতে ডেটা সোর্সটিকে ইনভ্যালিডেট করুন। - অনুরোধটির কারণে কোনো ত্রুটি ঘটলে,
MediatorResult.Errorরিটার্ন করুন।
- যদি লোড সফল হয় এবং প্রাপ্ত আইটেমের তালিকাটি খালি না হয়, তাহলে তালিকার আইটেমগুলো ডেটাবেসে সংরক্ষণ করুন এবং
জাভা
@NotNull @Override public Single<MediatorResult> loadSingle( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { // The network load method takes an optional after=<user.id> parameter. For // every page after the first, pass the last user ID to let it continue from // where it left off. For REFRESH, pass null to load the first page. String loadKey = null; switch (loadType) { case REFRESH: break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Single.just(new MediatorResult.Success(true)); case APPEND: User lastItem = state.lastItemOrNull(); // You must explicitly check if the last item is null when appending, // since passing null to networkService is only valid for initial load. // If lastItem is null it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return Single.just(new MediatorResult.Success(true)); } loadKey = lastItem.getId(); break; } return networkService.searchUsers(query, loadKey) .subscribeOn(Schedulers.io()) .map((Function<SearchUserResponse, MediatorResult>) response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getNextKey() == null); }) .onErrorResumeNext(e -> { if (e instanceof IOException || e instanceof HttpException) { return Single.just(new MediatorResult.Error(e)); } return Single.error(e); }); }
জাভা
@NotNull @Override public ListenableFuture<MediatorResult> loadFuture( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { // The network load method takes an optional after=<user.id> parameter. For // every page after the first, pass the last user ID to let it continue from // where it left off. For REFRESH, pass null to load the first page. String loadKey = null; switch (loadType) { case REFRESH: break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Futures.immediateFuture(new MediatorResult.Success(true)); case APPEND: User lastItem = state.lastItemOrNull(); // You must explicitly check if the last item is null when appending, // since passing null to networkService is only valid for initial load. // If lastItem is null it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return Futures.immediateFuture(new MediatorResult.Success(true)); } loadKey = lastItem.getId(); break; } ListenableFuture<MediatorResult> networkResult = Futures.transform( networkService.searchUsers(query, loadKey), response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getNextKey() == null); }, bgExecutor); ListenableFuture<MediatorResult> ioCatchingNetworkResult = Futures.catching( networkResult, IOException.class, MediatorResult.Error::new, bgExecutor ); return Futures.catching( ioCatchingNetworkResult, HttpException.class, MediatorResult.Error::new, bgExecutor ); }
initialize পদ্ধতিটি সংজ্ঞায়িত করুন
RemoteMediator ইমপ্লিমেন্টেশনগুলো initialize() মেথডটিকে ওভাররাইড করে ক্যাশ করা ডেটা পুরোনো হয়ে গেছে কিনা তা পরীক্ষা করতে পারে এবং রিমোট রিফ্রেশ ট্রিগার করা হবে কিনা সেই সিদ্ধান্ত নিতে পারে। এই মেথডটি যেকোনো লোডিং সম্পন্ন হওয়ার আগে রান করে, ফলে আপনি যেকোনো লোকাল বা রিমোট লোড ট্রিগার করার আগে ডাটাবেসকে ম্যানিপুলেট করতে পারেন (উদাহরণস্বরূপ, পুরোনো ডেটা মুছে ফেলার জন্য)।
যেহেতু initialize() একটি অ্যাসিঙ্ক্রোনাস ফাংশন, তাই ডাটাবেসে বিদ্যমান ডেটার প্রাসঙ্গিকতা নির্ধারণ করতে আপনি ডেটা লোড করতে পারেন। সবচেয়ে সাধারণ ক্ষেত্রে, ক্যাশ করা ডেটা শুধুমাত্র একটি নির্দিষ্ট সময়ের জন্য বৈধ থাকে। RemoteMediator পরীক্ষা করে দেখতে পারে যে এই মেয়াদ শেষ হওয়ার সময় পার হয়েছে কিনা, সেক্ষেত্রে Paging লাইব্রেরিকে ডেটা সম্পূর্ণরূপে রিফ্রেশ করতে হবে। initialize() এর ইমপ্লিমেন্টেশনগুলোতে নিম্নলিখিতভাবে একটি InitializeAction রিটার্ন করা উচিত:
- যেসব ক্ষেত্রে স্থানীয় ডেটা সম্পূর্ণরূপে রিফ্রেশ করার প্রয়োজন হয়,
initialize()InitializeAction.LAUNCH_INITIAL_REFRESHরিটার্ন করা উচিত। এর ফলেRemoteMediatorডেটা সম্পূর্ণরূপে পুনরায় লোড করার জন্য একটি রিমোট রিফ্রেশ সম্পাদন করে। যেকোনো রিমোটAPPENDবাPREPENDলোড পরবর্তী ধাপে যাওয়ার আগেREFRESHলোডটি সফল হওয়ার জন্য অপেক্ষা করে। - যেসব ক্ষেত্রে স্থানীয় ডেটা রিফ্রেশ করার প্রয়োজন হয় না,
initialize()InitializeAction.SKIP_INITIAL_REFRESHরিটার্ন করা উচিত। এর ফলেRemoteMediatorরিমোট রিফ্রেশ এড়িয়ে যায় এবং ক্যাশ করা ডেটা লোড করে।
জাভা
@NotNull @Override public Single<InitializeAction> initializeSingle() { long cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); return mUserDao.lastUpdatedSingle() .map(lastUpdatedMillis -> { if (System.currentTimeMillis() - lastUpdatedMillis <= cacheTimeout) { // Cached data is up-to-date, so there is no need to re-fetch // from the network. return InitializeAction.SKIP_INITIAL_REFRESH; } else { // Need to refresh cached data from network; returning // LAUNCH_INITIAL_REFRESH here will also block RemoteMediator's // APPEND and PREPEND from running until REFRESH succeeds. return InitializeAction.LAUNCH_INITIAL_REFRESH; } }); }
জাভা
@NotNull @Override public ListenableFuture<InitializeAction> initializeFuture() { long cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); return Futures.transform( mUserDao.lastUpdated(), lastUpdatedMillis -> { if (System.currentTimeMillis() - lastUpdatedMillis <= cacheTimeout) { // Cached data is up-to-date, so there is no need to re-fetch // from the network. return InitializeAction.SKIP_INITIAL_REFRESH; } else { // Need to refresh cached data from network; returning // LAUNCH_INITIAL_REFRESH here will also block RemoteMediator's // APPEND and PREPEND from running until REFRESH succeeds. return InitializeAction.LAUNCH_INITIAL_REFRESH; } }, mBgExecutor); }
একটি পেজার তৈরি করুন
অবশেষে, পেজ করা ডেটার স্ট্রিম সেট আপ করার জন্য আপনাকে অবশ্যই একটি Pager ইনস্ট্যান্স তৈরি করতে হবে। এটি একটি সাধারণ নেটওয়ার্ক ডেটা সোর্স থেকে Pager তৈরি করার মতোই, তবে দুটি জিনিস আপনাকে ভিন্নভাবে করতে হবে:
- সরাসরি
PagingSourceকনস্ট্রাক্টর পাস করার পরিবর্তে, আপনাকে অবশ্যই সেই কোয়েরি মেথডটি প্রদান করতে হবে যা DAO থেকে একটিPagingSourceঅবজেক্ট রিটার্ন করে। - আপনাকে অবশ্যই
remoteMediatorপ্যারামিটার হিসেবে আপনারRemoteMediatorইমপ্লিমেন্টেশনের একটি ইনস্ট্যান্স প্রদান করতে হবে।
জাভা
UserDao userDao = database.userDao(); Pager<Integer, User> pager = Pager( new PagingConfig(/* pageSize = */ 20), null, // initialKey, new ExampleRemoteMediator(query, database, networkService) () -> userDao.pagingSource(query));
জাভা
UserDao userDao = database.userDao(); Pager<Integer, User> pager = Pager( new PagingConfig(/* pageSize = */ 20), null, // initialKey new ExampleRemoteMediator(query, database, networkService, bgExecutor), () -> userDao.pagingSource(query));
রিমোট কীগুলি পরিচালনা করুন
রিমোট কী হলো এমন কী যা একটি RemoteMediator ইমপ্লিমেন্টেশন ব্যাকএন্ড সার্ভিসকে পরবর্তী কোন ডেটা লোড করতে হবে তা জানানোর জন্য ব্যবহার করে। সবচেয়ে সহজ ক্ষেত্রে, পেজ করা ডেটার প্রতিটি আইটেমের সাথে একটি রিমোট কী থাকে যা আপনি সহজেই রেফারেন্স করতে পারেন। তবে, যদি রিমোট কীগুলো স্বতন্ত্র আইটেমের সাথে সঙ্গতিপূর্ণ না হয়, তাহলে আপনাকে অবশ্যই সেগুলো আলাদাভাবে সংরক্ষণ করতে হবে এবং আপনার load() মেথডে সেগুলো পরিচালনা করতে হবে।
এই বিভাগে বর্ণনা করা হয়েছে কীভাবে সেইসব রিমোট কী সংগ্রহ, সংরক্ষণ এবং আপডেট করতে হয়, যেগুলো পৃথক আইটেমে সংরক্ষিত থাকে না।
আইটেম কী
এই বিভাগে স্বতন্ত্র আইটেমের সাথে সম্পর্কিত রিমোট কী নিয়ে কীভাবে কাজ করতে হয় তা বর্ণনা করা হয়েছে। সাধারণত, যখন কোনো এপিআই স্বতন্ত্র আইটেমকে কী হিসেবে ব্যবহার করে, তখন আইটেম আইডি একটি কোয়েরি প্যারামিটার হিসেবে পাঠানো হয়। প্যারামিটারের নামটি নির্দেশ করে যে সার্ভার প্রদত্ত আইডির আগের বা পরের আইটেমগুলো দিয়ে সাড়া দেবে কিনা। User মডেল ক্লাসের উদাহরণে, অতিরিক্ত ডেটার অনুরোধ করার সময় সার্ভারের id ফিল্ডটি একটি রিমোট কী হিসেবে ব্যবহৃত হয়।
যখন আপনার load() মেথডকে আইটেম-নির্দিষ্ট রিমোট কী পরিচালনা করতে হয়, তখন এই কীগুলো সাধারণত সার্ভার থেকে আনা ডেটার আইডি হয়ে থাকে। রিফ্রেশ অপারেশনের জন্য লোড কী-এর প্রয়োজন হয় না, কারণ এটি কেবল সবচেয়ে সাম্প্রতিক ডেটা নিয়ে আসে। একইভাবে, প্রিপেন্ড অপারেশনের জন্য কোনো অতিরিক্ত ডেটা আনার প্রয়োজন হয় না, কারণ রিফ্রেশ সবসময় সার্ভার থেকে নতুনতম ডেটা নিয়ে আসে।
তবে, অ্যাপেন্ড অপারেশনের জন্য একটি আইডি প্রয়োজন হয়। এর জন্য আপনাকে ডাটাবেস থেকে সর্বশেষ আইটেমটি লোড করতে হবে এবং ডেটার পরবর্তী পৃষ্ঠা লোড করার জন্য এর আইডি ব্যবহার করতে হবে। যদি ডাটাবেসে কোনো আইটেম না থাকে, তাহলে endOfPaginationReached মান true সেট করা হয়, যা নির্দেশ করে যে ডেটা রিফ্রেশ করা প্রয়োজন।
জাভা
@NotNull @Override public Single>MediatorResult< loadSingle( @NotNull LoadType loadType, @NotNull PagingState>Integer, User< state ) { // The network load method takes an optional String parameter. For every page // after the first, pass the String token returned from the previous page to // let it continue from where it left off. For REFRESH, pass null to load the // first page. Single>String< remoteKeySingle = null; switch (loadType) { case REFRESH: // Initial load should use null as the page key, so you can return null // directly. remoteKeySingle = Single.just(null); break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Single.just(new MediatorResult.Success(true)); case APPEND: User lastItem = state.lastItemOrNull(); // You must explicitly check if the last item is null when // appending, since passing null to networkService is only // valid for initial load. If lastItem is null it means no // items were loaded after the initial REFRESH and there are // no more items to load. if (lastItem == null) { return Single.just(new MediatorResult.Success(true)); } remoteKeySingle = Single.just(lastItem.getId()); break; } return remoteKeySingle .subscribeOn(Schedulers.io()) .flatMap((Function<String, Single<MediatorResult>>) remoteKey -> { return networkService.searchUsers(query, remoteKey) .map(response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getUsers().isEmpty()); }); }) .onErrorResumeNext(e -> { if (e instanceof IOException || e instanceof HttpException) { return Single.just(new MediatorResult.Error(e)); } return Single.error(e); }); }
জাভা
@NotNull @Override public ListenableFuture<MediatorResult> loadFuture( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { // The network load method takes an optional after=<user.id> parameter. // For every page after the first, pass the last user ID to let it continue // from where it left off. For REFRESH, pass null to load the first page. ResolvableFuture<String> remoteKeyFuture = ResolvableFuture.create(); switch (loadType) { case REFRESH: remoteKeyFuture.set(null); break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Futures.immediateFuture(new MediatorResult.Success(true)); case APPEND: User lastItem = state.lastItemOrNull(); // You must explicitly check if the last item is null when appending, // since passing null to networkService is only valid for initial load. // If lastItem is null it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return Futures.immediateFuture(new MediatorResult.Success(true)); } remoteKeyFuture.set(lastItem.getId()); break; } return Futures.transformAsync(remoteKeyFuture, remoteKey -> { ListenableFuture<MediatorResult> networkResult = Futures.transform( networkService.searchUsers(query, remoteKey), response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getUsers().isEmpty()); }, bgExecutor); ListenableFuture<MediatorResult> ioCatchingNetworkResult = Futures.catching( networkResult, IOException.class, MediatorResult.Error::new, bgExecutor ); return Futures.catching( ioCatchingNetworkResult, HttpException.class, MediatorResult.Error::new, bgExecutor ); }, bgExecutor); }
পৃষ্ঠা কী
এই অংশে এমন রিমোট কীগুলো নিয়ে কীভাবে কাজ করতে হয় তা বর্ণনা করা হয়েছে, যেগুলো কোনো নির্দিষ্ট আইটেমের সাথে সঙ্গতিপূর্ণ নয়।
রিমোট কী টেবিল যোগ করুন
যখন রিমোট কীগুলো তালিকার আইটেমগুলোর সাথে সরাসরি যুক্ত থাকে না, তখন সেগুলোকে স্থানীয় ডেটাবেসের একটি আলাদা টেবিলে সংরক্ষণ করাই শ্রেয়। একটি Room এনটিটি সংজ্ঞায়িত করুন যা রিমোট কীগুলোর একটি টেবিলকে উপস্থাপন করে:
জাভা
@Entity(tableName = "remote_keys") public class RemoteKey { public String label; public String nextKey; }
জাভা
@Entity(tableName = "remote_keys") public class RemoteKey { public String label; public String nextKey; }
আপনাকে RemoteKey এনটিটির জন্য একটি DAO-ও সংজ্ঞায়িত করতে হবে:
জাভা
@Dao interface RemoteKeyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertOrReplace(RemoteKey remoteKey); @Query("SELECT * FROM remote_keys WHERE label = :query") Single<RemoteKey> remoteKeyByQuerySingle(String query); @Query("DELETE FROM remote_keys WHERE label = :query") void deleteByQuery(String query); }
জাভা
@Dao interface RemoteKeyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertOrReplace(RemoteKey remoteKey); @Query("SELECT * FROM remote_keys WHERE label = :query") ListenableFuture<RemoteKey> remoteKeyByQueryFuture(String query); @Query("DELETE FROM remote_keys WHERE label = :query") void deleteByQuery(String query); }
রিমোট কী দিয়ে লোড করুন
যখন আপনার load() মেথডকে রিমোট পেজ কী (remote page keys) পরিচালনা করতে হয়, তখন RemoteMediator এর সাধারণ ব্যবহারের তুলনায় আপনাকে এটিকে নিম্নলিখিত উপায়ে ভিন্নভাবে সংজ্ঞায়িত করতে হবে:
- আপনার রিমোট কী টেবিলের জন্য DAO-এর একটি রেফারেন্স ধারণ করে এমন একটি অতিরিক্ত প্রপার্টি অন্তর্ভুক্ত করুন।
-
PagingStateব্যবহার না করে, রিমোট কী টেবিল কোয়েরি করে পরবর্তী কোন কী লোড করতে হবে তা নির্ধারণ করুন। - পেজ করা ডেটার পাশাপাশি নেটওয়ার্ক ডেটা সোর্স থেকে ফেরত আসা রিমোট কী-টিও সন্নিবেশ বা সংরক্ষণ করুন।
জাভা
@NotNull @Override public Single<MediatorResult> loadSingle( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { // The network load method takes an optional String parameter. For every page // after the first, pass the String token returned from the previous page to // let it continue from where it left off. For REFRESH, pass null to load the // first page. Single<RemoteKey> remoteKeySingle = null; switch (loadType) { case REFRESH: // Initial load should use null as the page key, so you can return null // directly. remoteKeySingle = Single.just(new RemoteKey(mQuery, null)); break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Single.just(new MediatorResult.Success(true)); case APPEND: // Query remoteKeyDao for the next RemoteKey. remoteKeySingle = mRemoteKeyDao.remoteKeyByQuerySingle(mQuery); break; } return remoteKeySingle .subscribeOn(Schedulers.io()) .flatMap((Function<RemoteKey, Single<MediatorResult>>) remoteKey -> { // You must explicitly check if the page key is null when appending, // since null is only valid for initial load. If you receive null // for APPEND, that means you have reached the end of pagination and // there are no more items to load. if (loadType != REFRESH && remoteKey.getNextKey() == null) { return Single.just(new MediatorResult.Success(true)); } return networkService.searchUsers(query, remoteKey.getNextKey()) .map(response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); remoteKeyDao.deleteByQuery(query); } // Update RemoteKey for this query. remoteKeyDao.insertOrReplace(new RemoteKey(query, response.getNextKey())); // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getNextKey() == null); }); }) .onErrorResumeNext(e -> { if (e instanceof IOException || e instanceof HttpException) { return Single.just(new MediatorResult.Error(e)); } return Single.error(e); }); }
জাভা
@NotNull @Override public ListenableFuture<MediatorResult> loadFuture( @NotNull LoadType loadType, @NotNull PagingState<Integer, User> state ) { // The network load method takes an optional after=<user.id> parameter. For // every page after the first, pass the last user ID to let it continue from // where it left off. For REFRESH, pass null to load the first page. ResolvableFuture<RemoteKey> remoteKeyFuture = ResolvableFuture.create(); switch (loadType) { case REFRESH: remoteKeyFuture.set(new RemoteKey(query, null)); break; case PREPEND: // In this example, you never need to prepend, since REFRESH will always // load the first page in the list. Immediately return, reporting end of // pagination. return Futures.immediateFuture(new MediatorResult.Success(true)); case APPEND: User lastItem = state.lastItemOrNull(); // You must explicitly check if the last item is null when appending, // since passing null to networkService is only valid for initial load. // If lastItem is null it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return Futures.immediateFuture(new MediatorResult.Success(true)); } // Query remoteKeyDao for the next RemoteKey. remoteKeyFuture.setFuture( remoteKeyDao.remoteKeyByQueryFuture(query)); break; } return Futures.transformAsync(remoteKeyFuture, remoteKey -> { // You must explicitly check if the page key is null when appending, // since null is only valid for initial load. If you receive null // for APPEND, that means you have reached the end of pagination and // there are no more items to load. if (loadType != LoadType.REFRESH && remoteKey.getNextKey() == null) { return Futures.immediateFuture(new MediatorResult.Success(true)); } ListenableFuture<MediatorResult> networkResult = Futures.transform( networkService.searchUsers(query, remoteKey.getNextKey()), response -> { database.runInTransaction(() -> { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query); remoteKeyDao.deleteByQuery(query); } // Update RemoteKey for this query. remoteKeyDao.insertOrReplace(new RemoteKey(query, response.getNextKey())); // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.getUsers()); }); return new MediatorResult.Success(response.getNextKey() == null); }, bgExecutor); ListenableFuture<MediatorResult> ioCatchingNetworkResult = Futures.catching( networkResult, IOException.class, MediatorResult.Error::new, bgExecutor ); return Futures.catching( ioCatchingNetworkResult, HttpException.class, MediatorResult.Error::new, bgExecutor ); }, bgExecutor); }
অতিরিক্ত সম্পদ
পেজিং লাইব্রেরি সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্সগুলো দেখুন:
কোডল্যাবস
নমুনা
{% হুবহু %}আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলেও লিঙ্কের লেখা প্রদর্শিত হয়।
- পৃষ্ঠা ডেটা লোড এবং প্রদর্শন করুন
- আপনার পেজিং বাস্তবায়ন পরীক্ষা করুন
- পেজিং ৩-এ স্থানান্তরিত করুন