यह पक्का करें कि आपका ऐप्लिकेशन इस्तेमाल किया जा सकता है, ताकि लोगों को बेहतर अनुभव मिल सके जब नेटवर्क कनेक्शन भरोसेमंद न हो या उपयोगकर्ता ऑफ़लाइन हो. एक तरफ़ नेटवर्क और स्थानीय डेटाबेस से एक ही समय पर पेज करने के लिए ऐसा किया जाता है. इस तरह, आपका ऐप्लिकेशन यूज़र इंटरफ़ेस (यूआई) को लोकल डेटाबेस कैश मेमोरी से ड्राइव करता है और सिर्फ़ डेटाबेस में ज़्यादा डेटा न होने पर, नेटवर्क से किए जाने वाले अनुरोध.
इस गाइड में यह माना गया है कि आपको रूम परसिस्टेंस लाइब्रेरी और पेजिंग के बुनियादी इस्तेमाल के साथ लाइब्रेरी पर जाएं.
कोऑर्डिनेट डेटा लोड
पेजिंग लाइब्रेरी
RemoteMediator
कॉम्पोनेंट
का पालन नहीं करता है. RemoteMediator
, पेजिंग लाइब्रेरी से सिग्नल के तौर पर काम करता है
जब ऐप्लिकेशन में कैश मेमोरी में सेव किया गया डेटा खत्म हो जाता है. इस सिग्नल का इस्तेमाल, पेज को लोड करने के लिए किया जा सकता है
नेटवर्क से अतिरिक्त डेटा इकट्ठा कर सकता है और उसे लोकल डेटाबेस में सेव कर सकता है, जहां
PagingSource
इसे लोड कर सकता है और
उसे यूज़र इंटरफ़ेस (यूआई) में उपलब्ध कराएं, ताकि उसे दिखाया जा सके.
जब अतिरिक्त डेटा की ज़रूरत होती है, पेजिंग लाइब्रेरी
load()
तरीका
RemoteMediator
को लागू करना. यह फ़ंक्शन निलंबित किया जा रहा है, इसलिए यह सुरक्षित है
लंबे समय तक चलने वाले काम के लिए. आम तौर पर, यह फ़ंक्शन नए डेटा को यहां से फ़ेच करता है
नेटवर्क सोर्स को सेव करता है और उसे लोकल स्टोरेज में सेव करता है.
यह प्रोसेस नए डेटा के साथ काम करती है. हालांकि, समय के साथ डेटाबेस में सेव किए गए डेटा के आधार पर
के लिए अमान्य होना ज़रूरी है, जैसे कि जब उपयोगकर्ता मैन्युअल रूप से रीफ़्रेश ट्रिगर करता हो. यह
LoadType
से दिखाया जाता है
प्रॉपर्टी load()
तरीके को पास की जाती है. LoadType
,
RemoteMediator
इसे मौजूदा डेटा को रीफ़्रेश करने या फ़ेच करने की ज़रूरत है या नहीं
वह अतिरिक्त डेटा जिसे मौजूदा सूची में जोड़ना या उससे पहले जोड़ना ज़रूरी है.
इस तरह, RemoteMediator
यह पक्का करता है कि आपका ऐप्लिकेशन वह डेटा लोड करे
उपयोगकर्ता सही क्रम में देखना चाहते हैं.
पेजिंग लाइफ़साइकल
![](https://developer.android.com/static/topic/libraries/architecture/images/paging3-base-lifecycle.png?authuser=19&hl=hi)
सीधे नेटवर्क से पेज करते समय, PagingSource
डेटा लोड करता है और
नतीजे के तौर पर
LoadResult
ऑब्जेक्ट है. PagingSource
लागू करने की प्रक्रिया
Pager
से
pagingSourceFactory
पैरामीटर.
यूज़र इंटरफ़ेस (यूआई) को नए डेटा की ज़रूरत होने पर, Pager
load()
तरीका
PagingSource
और इसकी स्ट्रीम दिखाता है
PagingData
ऐसे ऑब्जेक्ट जो
नए डेटा को इनकैप्सुलेट कर सकता है. आम तौर पर, हर PagingData
ऑब्जेक्ट को
दिखाने के लिए यूज़र इंटरफ़ेस (यूआई) पर भेजे जाने से पहले ViewModel
.
![](https://developer.android.com/static/topic/libraries/architecture/images/paging3-layered-lifecycle.png?authuser=19&hl=hi)
RemoteMediator
, इस डेटा फ़्लो को बदल देता है. PagingSource
अब भी डेटा लोड करता है;
हालांकि, जब पेज किया गया डेटा खत्म हो जाता है, तो पेजिंग लाइब्रेरी
नेटवर्क सोर्स से नया डेटा लोड करने के लिए RemoteMediator
. RemoteMediator
नए डेटा को लोकल डेटाबेस में सेव करता है, इसलिए इन-मेमोरी कैश मेमोरी में
ViewModel
की ज़रूरत नहीं है. आखिर में, PagingSource
खुद को अमान्य कर देता है और
Pager
, डेटाबेस से नया डेटा लोड करने के लिए एक नया इंस्टेंस बनाता है.
बुनियादी इस्तेमाल
मान लें कि आपको अपने ऐप्लिकेशन में, आइटम के कीबोर्ड की मदद से User
आइटम वाले पेज लोड करने हैं
रूम डेटाबेस में स्टोर किए गए लोकल कैश मेमोरी में नेटवर्क डेटा सोर्स.
RemoteMediator
को लागू करने से, पेज पर मौजूद डेटा को नेटवर्क से
डेटाबेस से कनेक्ट होता है, लेकिन सीधे यूज़र इंटरफ़ेस (यूआई) में डेटा लोड नहीं करता. इसके बजाय, ऐप्लिकेशन
के स्रोत के रूप में
सच होना चाहिए. दूसरे शब्दों में, सिर्फ़ ऐप्लिकेशन में
दिखाता है कि डेटाबेस में कैश किया गया डेटा क्या है. PagingSource
लागू करने की प्रक्रिया (उदाहरण के लिए, रूम से जनरेट की गई सुविधा), कैश मेमोरी में सेव किए गए डेटा को लोड होने में मदद करती है
डेटाबेस से यूज़र इंटरफ़ेस (यूआई) में ले जाया जाता है.
रूम की इकाइयां बनाएं
पहला कदम है रूम परसिस्टेंस
लाइब्रेरी का इस्तेमाल किया जा सकता है, ताकि
नेटवर्क डेटा सोर्स से पेज किए गए डेटा की लोकल कैश मेमोरी. इसके साथ शुरू करें:
RoomDatabase
को लागू करना
जैसा कि स्थानीय डेटाबेस में डेटा सेव करने के लिए, इसका इस्तेमाल किया गया है
कमरा.
इसके बाद, रूम की इकाई तय करें, ताकि सूची में मौजूद आइटम की टेबल में इस बारे में जानकारी दी जा सके
रूम की इकाइयों का इस्तेमाल करके, डेटा तय करना.
इसे id
फ़ील्ड को प्राइमरी पासकोड के तौर पर जोड़ें. साथ ही, किसी अन्य फ़ील्ड के लिए इस्तेमाल करें
वह जानकारी जो आपके सूची आइटम में शामिल है.
Kotlin
@Entity(tableName = "users") data class User(val id: String, val label: String)
Java
@Entity(tableName = "users") public class User { public String id; public String label; }
Java
@Entity(tableName = "users") public class User { public String id; public String label; }
आपको इस रूम की इकाई के लिए, डेटा ऐक्सेस ऑब्जेक्ट (डीएओ) को भी इस तरह तय करना होगा रूम का इस्तेमाल करके डेटा ऐक्सेस करना डीएओ. लिस्ट आइटम के लिए डीएओ इकाई में नीचे दिए गए तरीके शामिल होने चाहिए:
insertAll()
तरीका, जो टेबल में आइटम की सूची जोड़ता है.- एक ऐसी तरीका जो क्वेरी स्ट्रिंग को पैरामीटर के रूप में लेता है और नतीजे के तौर पर
नतीजों की सूची के लिए
PagingSource
ऑब्जेक्ट. इस तरह से,Pager
ऑब्जेक्ट ये काम कर सकता है इस टेबल का इस्तेमाल, पेज किए गए डेटा के सोर्स के तौर पर करें. clearAll()
तरीका, जो टेबल का पूरा डेटा मिटाता है.
Kotlin
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(users: List<User>) @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> @Query("DELETE FROM users") suspend fun clearAll() }
Java
@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(); }
Java
@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 को लागू करना
नेटवर्क से ज़्यादा डेटा लोड करना तब RemoteMediator
की मुख्य भूमिका होती है, जब
या तो Pager
का डेटा खत्म हो गया है या मौजूदा डेटा अमान्य है. यह
में एक load()
तरीका शामिल है, जिसे लोडिंग के बारे में बताने के लिए, आपको इसे बदलना होगा
व्यवहार.
आम तौर पर, RemoteMediator
को लागू करने के तरीके में ये पैरामीटर शामिल होते हैं:
query
: यह क्वेरी स्ट्रिंग जिससे तय होता है कि बैकएंड से कौनसा डेटा वापस पाना है सेवा.database
: रूम का डेटाबेस, जो लोकल कैश मेमोरी के तौर पर काम करता है.networkService
: बैकएंड सेवा के लिए एपीआई इंस्टेंस.
RemoteMediator<Key, Value>
लागू करें. Key
टाइप और
Value
टाइप वही होना चाहिए जो
PagingSource
उसी नेटवर्क डेटा स्रोत के लिए भी इस्तेमाल किया जा सकता है. अगर आपको ज़्यादा जानकारी चाहिए, तो
प्रकार पैरामीटर चुनकर, कुंजी और मान चुनें देखें
टाइप.
Kotlin
@OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { // ... } }
Java
@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 ) { ... } }
Java
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
को अमान्य कर दिया जाएगा. कुछ लाइब्रेरी जो पेजिंग की सुविधा देती हैं (जैसे रूम)
अमान्य 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
लौटाया जा सकता है.
- अगर लोड हो जाता है और आइटम की मिली सूची खाली नहीं है,
फिर सूची के आइटम को डेटाबेस में स्टोर करें और
Kotlin
override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // 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. val loadKey = when (loadType) { LoadType.REFRESH -> null // 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. LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { val 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 MediatorResult.Success( endOfPaginationReached = true ) } lastItem.id } } // Suspending network load via Retrofit. This doesn't need to be // wrapped in a withContext(Dispatcher.IO) { ... } block since // Retrofit's Coroutine CallAdapter dispatches on a worker // thread. val response = networkService.searchUsers( query = query, after = loadKey ) database.withTransaction { 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.users) } MediatorResult.Success( endOfPaginationReached = response.nextKey == null ) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } }
Java
@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); }); }
Java
@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 ); }
शुरू करने का तरीका तय करें
RemoteMediator
लागू करने का तरीका भी ओवरराइड हो सकता है
initialize()
यह पता लगाने का तरीका कि कैश मेमोरी में सेव किया गया डेटा पुराना है या नहीं. साथ ही, यह भी तय किया जा सकता है कि
रिमोट रीफ़्रेश. यह तरीका, लोड होने से पहले काम करता है. इससे आपको ये काम करने में मदद मिलती है
किसी भी डेटाबेस को ट्रिगर करने से पहले उसमें बदलाव करना (उदाहरण के लिए, पुराना डेटा मिटाने के लिए)
लोकल या रिमोट लोड.
initialize()
एक एसिंक्रोनस फ़ंक्शन है, इसलिए आप
तय करें कि डेटाबेस में मौजूद डेटा कितने काम का है. सबसे ज़्यादा खाया गया खाना
मामला यह है कि कैश मेमोरी में सेव किया गया डेटा एक तय अवधि तक ही मान्य होता है. कॉन्टेंट बनाने
RemoteMediator
जांच कर सकता है कि समयसीमा खत्म हुई है या नहीं. इसमें
जब पेजिंग लाइब्रेरी को डेटा पूरी तरह से रीफ़्रेश करना पड़े. लागू
initialize()
को इस तरह InitializeAction
देना चाहिए:
- उन मामलों में जहां लोकल डेटा को पूरी तरह से रीफ़्रेश करने की ज़रूरत होती है,
initialize()
वापस आना चाहिएInitializeAction.LAUNCH_INITIAL_REFRESH
. इससे पूरी तरह फिर से लोड होने के लिए,RemoteMediator
रिमोट तरीके से रीफ़्रेश करता है के लिए इस्तेमाल किया जा सकता है. कोई भी रिमोटAPPEND
याPREPEND
लोड,REFRESH
के लोड होने का इंतज़ार करता है ताकि आप आगे बढ़ने से पहले सफल रहें. - उन मामलों में जहां स्थानीय डेटा को रीफ़्रेश करने की ज़रूरत नहीं होती है,
initialize()
वापस आना चाहिएInitializeAction.SKIP_INITIAL_REFRESH
. इससेRemoteMediator
, रिमोट रीफ़्रेश को स्किप करता है और कैश मेमोरी में सेव किया गया डेटा.
Kotlin
override suspend fun initialize(): InitializeAction { val cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS) return if (System.currentTimeMillis() - db.lastUpdated() <= cacheTimeout) { // Cached data is up-to-date, so there is no need to re-fetch // from the network. 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. InitializeAction.LAUNCH_INITIAL_REFRESH } }
Java
@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; } }); }
Java
@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
कंस्ट्रक्टर पास करने के बजाय, आपको क्वेरी का वह तरीका जो डीएओ सेPagingSource
ऑब्जेक्ट दिखाता है. - आपको अपने
RemoteMediator
को लागू करने का एक इंस्टेंस इस तौर पर देना होगाremoteMediator
पैरामीटर.
Kotlin
val userDao = database.userDao() val pager = Pager( config = PagingConfig(pageSize = 50) remoteMediator = ExampleRemoteMediator(query, database, networkService) ) { userDao.pagingSource(query) }
Java
UserDao userDao = database.userDao(); Pager<Integer, User> pager = Pager( new PagingConfig(/* pageSize = */ 20), null, // initialKey, new ExampleRemoteMediator(query, database, networkService) () -> userDao.pagingSource(query));
Java
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
लागू करने की प्रक्रिया से initialize()
तरीका वापस आता है
LAUNCH_INITIAL_REFRESH
, डेटा पुराना है और इसे नए डेटा से बदलना होगा
डेटा शामिल है. लोड करने के PREPEND
या APPEND
अनुरोध करने पर, आपको रिमोट के लिए इंतज़ार करना होगा
सफल होने के लिए REFRESH
लोड करें. ऐसा इसलिए हुआ, क्योंकि PREPEND
या APPEND
अनुरोध
REFRESH
अनुरोध से पहले कतारबद्ध, संभव है कि PagingState
उन लोड कॉल को पास किया जाने वाला डेटा उनके चलने से पुराने हो जाएंगे.
डेटा को डिवाइस पर सेव करने के तरीके के आधार पर, आपका ऐप्लिकेशन गै़र-ज़रूरी डेटा को अनदेखा कर सकता है
अगर कैश मेमोरी में सेव किए गए डेटा में बदलावों की वजह से अमान्य होने और नया डेटा फ़ेच करने की वजह होती है, तो अनुरोध करता है.
उदाहरण के लिए, चैट रूम किसी भी तरह के डेटा को शामिल करने से जुड़ी क्वेरी को अमान्य कर देता है. इसका मतलब है कि
रीफ़्रेश किए गए डेटा वाले PagingSource
ऑब्जेक्ट को लोड होने की मंज़ूरी नहीं दी गई है
डेटाबेस में नया डेटा डाले जाने पर अनुरोध करता है.
डेटा सिंक करने की इस समस्या को हल करना ज़रूरी है, ताकि यह पक्का किया जा सके कि उपयोगकर्ता आपको सबसे काम का और अप-टू-डेट डेटा दिखेगा. सबसे अच्छा समाधान अक्सर इस पर निर्भर करता है कि जिस तरीके से नेटवर्क डेटा सोर्स, डेटा को पेज करता है. किसी भी स्थिति में, रिमोट बटन की मदद से, सबसे हाल ही के पेज के बारे में जानकारी सेव की जा सकती है सर्वर से अनुरोध किया गया. आपका ऐप्लिकेशन इस जानकारी का इस्तेमाल, यह पता लगाने के लिए कर सकता है कि अगला लोड करने के लिए, डेटा के सही पेज का अनुरोध करें.
रिमोट बटन मैनेज करें
रिमोट कुंजियां ऐसी कुंजियां होती हैं जिनका उपयोग RemoteMediator
लागू करने के लिए किया जाता है
बैकएंड सेवा के मुताबिक, आगे किस डेटा को लोड करना है. सबसे सामान्य मामले में, इसका प्रत्येक आइटम
पेज वाले डेटा में एक रिमोट कुंजी शामिल होती है, जिसका संदर्भ आसानी से दिया जा सकता है. हालांकि, अगर
रिमोट बटन अलग-अलग आइटम से मेल नहीं खाते. इसलिए, आपको उन्हें सेव करना होगा
और उन्हें अपने load()
तरीके में मैनेज करें.
इस सेक्शन में उन रिमोट कुंजियों को इकट्ठा, सेव, और अपडेट करने के तरीके के बारे में बताया गया है जिन्हें इन आइटम को अलग-अलग आइटम में सेव नहीं किया जाता.
आइटम कुंजियां
इस सेक्शन में बताया गया है कि
अलग-अलग आइटम. आम तौर पर, जब अलग-अलग आइटम को एपीआई पासकोड से बंद किया जाता है, तो आइटम
आईडी को क्वेरी पैरामीटर के रूप में पास किया जाता है. पैरामीटर के नाम से पता चलता है कि
सर्वर को दिए गए आईडी से पहले या बाद में आइटम के साथ जवाब देना चाहिए. उदाहरण में
User
मॉडल क्लास के में से, सर्वर का id
फ़ील्ड रिमोट के तौर पर इस्तेमाल किया जाता है
कुंजी का इस्तेमाल करें.
जब आपके load()
वाले तरीके को किसी खास आइटम के रिमोट बटन को मैनेज करने की ज़रूरत होती है, तब ये बटन
आम तौर पर, सर्वर से फ़ेच किए गए डेटा का आईडी होता है. कार्रवाइयां रीफ़्रेश करना
को लोड कुंजी की ज़रूरत नहीं होती, क्योंकि वे सिर्फ़ सबसे हाल का डेटा हासिल करते हैं.
इसी तरह, शुरुआत में की जाने वाली कार्रवाइयों को कोई और डेटा फ़ेच करने की ज़रूरत नहीं होती, क्योंकि
रीफ़्रेश हमेशा सर्वर से सबसे नया डेटा फ़ेच करता है.
हालांकि, जोड़ने के लिए एक आईडी की ज़रूरत होती है. इसके लिए आपको अंतिम बार लोड किया जाने वाला
आइटम को डेटाबेस से हटाता है और उसके आईडी का इस्तेमाल करके डेटा का अगला पेज लोड करता है. अगर वहां
डेटाबेस में कोई आइटम नहीं हैं, तो endOfPaginationReached
'सही' पर सेट होता है,
यह बताती है कि डेटा को रीफ़्रेश करने की ज़रूरत है.
Kotlin
@OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // 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. val loadKey = when (loadType) { LoadType.REFRESH -> null // 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. LoadType.PREPEND -> return MediatorResult.Success( endOfPaginationReached = true ) // Get the last User object id for the next RemoteKey. LoadType.APPEND -> { val 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 MediatorResult.Success( endOfPaginationReached = true ) } lastItem.id } } // Suspending network load via Retrofit. This doesn't need to // be wrapped in a withContext(Dispatcher.IO) { ... } block // since Retrofit's Coroutine CallAdapter dispatches on a // worker thread. val response = networkService.searchUsers(query, loadKey) // Store loaded data, and next key in transaction, so that // they're always consistent. database.withTransaction { 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.users) } // End of pagination has been reached if no users are returned from the // service MediatorResult.Success( endOfPaginationReached = response.users.isEmpty() ) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } }
Java
@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); }); }
Java
@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); }
पेज कुंजियां
इस सेक्शन में उन रिमोट कुंजियों के साथ काम करने का तरीका बताया गया है जो इनसे मेल नहीं खातीं अलग-अलग आइटम.
रिमोट की टेबल जोड़ें
जब रिमोट बटन, सूची में मौजूद आइटम से सीधे तौर पर नहीं जुड़े होते हैं, तो उन्हें लोकल डेटाबेस में एक अलग टेबल में स्टोर कर देता है. रूम की ऐसी इकाई तय करें जो रिमोट कुंजियों की टेबल दिखाता है:
Kotlin
@Entity(tableName = "remote_keys") data class RemoteKey(val label: String, val nextKey: String?)
Java
@Entity(tableName = "remote_keys") public class RemoteKey { public String label; public String nextKey; }
Java
@Entity(tableName = "remote_keys") public class RemoteKey { public String label; public String nextKey; }
आपको RemoteKey
इकाई के लिए भी डीएओ तय करना होगा:
Kotlin
@Dao interface RemoteKeyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOrReplace(remoteKey: RemoteKey) @Query("SELECT * FROM remote_keys WHERE label = :query") suspend fun remoteKeyByQuery(query: String): RemoteKey @Query("DELETE FROM remote_keys WHERE label = :query") suspend fun deleteByQuery(query: String) }
Java
@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); }
Java
@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()
तरीके को रिमोट पेज कुंजियों को मैनेज करने की ज़रूरत हो, तो आपको इसे तय करना होगा
के बेसिक इस्तेमाल की तुलना में इन तरीकों से अलग है
RemoteMediator
:
- एक ऐसी अतिरिक्त प्रॉपर्टी शामिल करें जिसमें आपके कारोबार के डीएओ का रेफ़रंस हो रिमोट कुंजी की टेबल.
- इसके बजाय, रिमोट कुंजी की टेबल पर क्वेरी करके, यह तय करें कि अगली किस कुंजी को लोड करना है
PagingState
का इस्तेमाल करके. - नेटवर्क डेटा सोर्स से वापस मिली रिमोट कुंजी को इसमें डालें या सेव करें पेज पर मौजूद डेटा को जोड़ दिया जाता है.
Kotlin
@OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() val remoteKeyDao = database.remoteKeyDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // 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. val loadKey = when (loadType) { LoadType.REFRESH -> null // 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. LoadType.PREPEND -> return MediatorResult.Success( endOfPaginationReached = true ) // Query remoteKeyDao for the next RemoteKey. LoadType.APPEND -> { val remoteKey = database.withTransaction { remoteKeyDao.remoteKeyByQuery(query) } // 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 (remoteKey.nextKey == null) { return MediatorResult.Success( endOfPaginationReached = true ) } remoteKey.nextKey } } // Suspending network load via Retrofit. This doesn't need to // be wrapped in a withContext(Dispatcher.IO) { ... } block // since Retrofit's Coroutine CallAdapter dispatches on a // worker thread. val response = networkService.searchUsers(query, loadKey) // Store loaded data, and next key in transaction, so that // they're always consistent. database.withTransaction { if (loadType == LoadType.REFRESH) { remoteKeyDao.deleteByQuery(query) userDao.deleteByQuery(query) } // Update RemoteKey for this query. remoteKeyDao.insertOrReplace( RemoteKey(query, response.nextKey) ) // Insert new users into database, which invalidates the // current PagingData, allowing Paging to present the updates // in the DB. userDao.insertAll(response.users) } MediatorResult.Success( endOfPaginationReached = response.nextKey == null ) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } }
Java
@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); }); }
Java
@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); }
मौजूदा जगह पर रीफ़्रेश करें
अगर आपके ऐप्लिकेशन को सूची में सबसे ऊपर से सिर्फ़ नेटवर्क रीफ़्रेश करने की ज़रूरत है, तो
तो पिछले उदाहरण में बताया गया है, तो आपके RemoteMediator
प्रीपेंड लोड व्यवहार.
हालांकि, अगर आपके ऐप्लिकेशन को नेटवर्क से इंंक्रीमेंटल लोडिंग की सुविधा चाहिए, तो
स्थानीय डेटाबेस में है, तो आपको 'पेज पर नंबर डालें' फिर से शुरू करने के लिए सहायता देनी होगी
जहां उपयोगकर्ता पेज पर सबसे नीचे स्क्रोल कर सकता है. कमरे का PagingSource
लागू करने की प्रक्रिया इसे आपके लिए संभाल लेती है, लेकिन अगर आप Room का इस्तेमाल नहीं कर रहे हैं, तो आप ये काम कर सकते हैं
PagingSource.getRefreshKey()
.
getRefreshKey()
को लागू करने के उदाहरण के लिए,
PagingSource.
चौथी इमेज में, लोकल डेटाबेस से डेटा लोड करने की प्रोसेस दिखाई गई है. और फिर डेटाबेस के डेटा खत्म हो जाने पर नेटवर्क से.
अन्य संसाधन
पेजिंग लाइब्रेरी के बारे में ज़्यादा जानने के लिए, इन अतिरिक्त संसाधनों को देखें:
कोड लैब
सैंपल
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- पेज किया गया डेटा लोड करना और दिखाना
- पेजिंग लागू करने की प्रक्रिया की जांच करना
- पेज 3 पर माइग्रेट करना