Kavramlar ve Jetpack Compose uygulaması
Ağ bağlantıları güvenilir olmadığında veya kullanıcı çevrimdışıyken uygulamanızın kullanılabildiğinden emin olarak daha iyi bir kullanıcı deneyimi sunun. Bunu yapmanın bir yolu, aynı anda hem ağdan hem de yerel bir veritabanından sayfa oluşturmaktır. Bu şekilde, uygulamanız kullanıcı arayüzünü yerel bir veritabanı önbelleğinden yönlendirir ve yalnızca veritabanında başka veri kalmadığında ağa istekte bulunur.
Bu kılavuzda, Room kalıcılık kitaplığı ve Paging kitaplığının temel kullanımı hakkında bilgi sahibi olduğunuz varsayılmaktadır.
Veri yüklemelerini koordine etme
Paging kitaplığı, bu kullanım alanı için RemoteMediator bileşenini sağlar. RemoteMediator, uygulama önbelleğe alınmış verileri tükendiğinde Paging kitaplığından gelen bir sinyal görevi görür. Bu sinyali kullanarak ağdan ek veriler yükleyebilir ve bunları yerel veritabanında saklayabilirsiniz. PagingSource, bu verileri yükleyip kullanıcı arayüzünde görüntülenmek üzere sağlayabilir.
Ek veriye ihtiyaç duyulduğunda Paging kitaplığı, RemoteMediator uygulamasından load() yöntemini çağırır. Bu, askıya alma işlevi olduğundan uzun süren işlemleri güvenle gerçekleştirebilirsiniz. Bu işlev genellikle yeni verileri bir ağ kaynağından getirir ve yerel depolama alanına kaydeder.
Bu işlem yeni verilerle çalışır ancak zamanla veritabanında depolanan verilerin geçersiz kılınması gerekir. Örneğin, kullanıcı manuel olarak yenileme işlemi başlattığında bu durum ortaya çıkar. Bu, load() yöntemine iletilen LoadType özelliğiyle gösterilir. LoadType, RemoteMediator'e mevcut verileri yenilemesi mi yoksa mevcut listeye eklenmesi veya listenin başına eklenmesi gereken ek verileri mi getirmesi gerektiğini bildirir.
Bu şekilde RemoteMediator, uygulamanızın kullanıcıların görmek istediği verileri uygun sırada yüklemesini sağlar.
Sayfalama yaşam döngüsü
Doğrudan ağdan sayfalama yapıldığında PagingSource verileri yükler ve LoadResult nesnesini döndürür. PagingSource uygulaması, pagingSourceFactory parametresi aracılığıyla Pager işlevine iletilir.
Kullanıcı arayüzü yeni veriler gerektirdiğinden Pager, PagingSource'den load() yöntemini çağırır ve yeni verileri kapsayan bir PagingData nesne akışı döndürür. Her PagingData nesnesi, görüntülenmek üzere kullanıcı arayüzüne gönderilmeden önce genellikle ViewModel içinde önbelleğe alınır.
RemoteMediator bu veri akışını değiştirir. PagingSource, verileri yüklemeye devam eder ancak sayfalandırılmış veriler tükendiğinde Paging kitaplığı, ağ kaynağındaki yeni verileri yüklemek için RemoteMediator'yi tetikler. RemoteMediator, yeni verileri yerel veritabanında depolar. Bu nedenle, ViewModel içinde bellek içi önbellek kullanılması gereksizdir. Son olarak, PagingSource kendisini geçersiz kılar ve Pager, veritabanından yeni verileri yüklemek için yeni bir örnek oluşturur.
Temel kullanım
Uygulamanızın, öğe anahtarlı bir ağ veri kaynağından alınan User öğe sayfalarını Room veritabanında depolanan yerel bir önbelleğe yüklemesini istediğinizi varsayalım.
RemoteMediator uygulaması, ağdan alınan sayfalandırılmış verilerin veritabanına yüklenmesine yardımcı olur ancak verileri doğrudan kullanıcı arayüzüne yüklemez. Bunun yerine uygulama, veritabanını doğruluk kaynağı olarak kullanır. Diğer bir deyişle, uygulama yalnızca veri tabanında önbelleğe alınmış verileri gösterir. PagingSource uygulaması (örneğin, Room tarafından oluşturulan bir uygulama), önbelleğe alınmış verilerin veritabanından kullanıcı arayüzüne yüklenmesini sağlar.
Oda varlıkları oluşturma
İlk adım, ağ veri kaynağındaki sayfalandırılmış verilerin yerel önbelleğini tutan bir veritabanı tanımlamak için Room persistence
kitaplığını kullanmaktır. RoomDatabase'ı Room kullanarak verileri yerel veritabanına kaydetme bölümünde açıklandığı şekilde uygulayarak başlayın.
Ardından, Oda varlıklarını kullanarak verileri tanımlama bölümünde açıklandığı gibi, liste öğelerinin tablosunu temsil edecek bir Oda varlığı tanımlayın.
Listeleme öğelerinizin içerdiği diğer bilgiler için alanların yanı sıra birincil anahtar olarak id alanını ekleyin.
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; }
Ayrıca, Accessing data using Room DAOs (Room DAO'larını kullanarak verilere erişme) bölümünde açıklandığı gibi bu Room öğesi için bir veri erişimi nesnesi (DAO) tanımlamanız gerekir. Liste öğesi varlığı için DAO, aşağıdaki yöntemleri içermelidir:
- Bir öğe listesini tabloya ekleyen
insertAll()yöntemi. - Sorgu dizesini parametre olarak alan ve sonuç listesi için bir
PagingSourcenesnesi döndüren bir yöntem. Bu şekilde, birPagernesnesi bu tabloyu sayfalandırılmış veri kaynağı olarak kullanabilir. - Tablonun tüm verilerini silen bir
clearAll()yöntemi.
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 uygulama
RemoteMediator'nın temel görevi, Pager'nın verisi bittiğinde veya mevcut veriler geçersiz kılındığında ağdan daha fazla veri yüklemektir. Yükleme davranışını tanımlamak için geçersiz kılmanız gereken bir load() yöntemi içerir.
Tipik bir RemoteMediator uygulaması aşağıdaki parametreleri içerir:
query: Arka uç hizmetinden hangi verilerin alınacağını tanımlayan bir sorgu dizesi.database: Yerel önbellek olarak kullanılan Room veritabanı.networkService: Arka uç hizmeti için bir API örneği.
RemoteMediator<Key, Value> uygulaması oluşturun. Key türü ve Value türü, aynı ağ veri kaynağına karşı bir PagingSource tanımlıyormuşsunuz gibi aynı olmalıdır. Tür parametrelerini seçme hakkında daha fazla bilgi için Anahtar ve değer türlerini seçme başlıklı makaleyi inceleyin.
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() yöntemi, destekleyen veri kümesini güncellemekten ve PagingSource öğesini geçersiz kılmaktan sorumludur. Sayfalandırmayı destekleyen bazı kitaplıklar (ör. Room), uyguladıkları PagingSource nesnelerinin geçersiz kılınmasını otomatik olarak işler.
load() yöntemi iki parametre alır:
- Şu ana kadar yüklenen sayfalar, en son erişilen dizin ve
PagingConfignesnesi hakkında bilgiler içerenPagingState. - Yükleme türünü belirten
LoadType:REFRESH,APPENDveyaPREPEND.
load() yönteminin döndürdüğü değer bir MediatorResult nesnesidir. MediatorResult, MediatorResult.Error (hata açıklamasını içerir) veya MediatorResult.Success (yüklenecek daha fazla veri olup olmadığını belirten bir sinyali içerir) olabilir.
load() yöntemi aşağıdaki adımları gerçekleştirmelidir:
- Yükleme türüne ve şu ana kadar yüklenen verilere bağlı olarak ağdan yüklenecek sayfayı belirleyin.
- Ağ isteğini tetikleyin.
- Yükleme işleminin sonucuna bağlı olarak aşağıdaki işlemleri yapın:
- Yükleme başarılı olursa ve alınan öğe listesi boş değilse liste öğelerini veritabanında saklayın ve
MediatorResult.Success(endOfPaginationReached = false)değerini döndürün. Veriler depolandıktan sonra, yeni veriler hakkında Paging kitaplığını bilgilendirmek için veri kaynağını geçersiz kılın. - Yükleme başarılıysa ve alınan öğe listesi boşsa veya son sayfa diziniyse
MediatorResult.Success(endOfPaginationReached = true)değerini döndürün. Veriler depolandıktan sonra, yeni veriler hakkında Paging kitaplığını bilgilendirmek için veri kaynağını geçersiz kılın. - İstek hataya neden olursa
MediatorResult.Errordeğerini döndürün.
- Yükleme başarılı olursa ve alınan öğe listesi boş değilse liste öğelerini veritabanında saklayın ve
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 ); }
initialize yöntemini tanımlayın
RemoteMediator uygulamaları, önbelleğe alınmış verilerin güncel olup olmadığını kontrol etmek için initialize() yöntemini de geçersiz kılabilir ve uzaktan yenileme işleminin tetiklenip tetiklenmeyeceğine karar verebilir. Bu yöntem, herhangi bir yükleme işlemi yapılmadan önce çalışır. Böylece, yerel veya uzak yüklemeleri tetiklemeden önce veritabanını değiştirebilirsiniz (örneğin, eski verileri temizlemek için).
initialize() işlevi eşzamansız olduğundan, veritabanındaki mevcut verilerin alaka düzeyini belirlemek için veri yükleyebilirsiniz. En yaygın durum, önbelleğe alınan verilerin yalnızca belirli bir süre için geçerli olmasıdır. RemoteMediator, bu son kullanma süresinin geçip geçmediğini kontrol edebilir. Bu durumda Paging kitaplığının verileri tamamen yenilemesi gerekir. initialize() uygulamaları, aşağıdaki şekilde bir InitializeAction döndürmelidir:
- Yerel verilerin tamamen yenilenmesi gerektiği durumlarda
initialize(),InitializeAction.LAUNCH_INITIAL_REFRESHdeğerini döndürmelidir. Bu işlem,RemoteMediatortarayıcısının verileri tamamen yeniden yüklemek için uzaktan yenileme yapmasına neden olur. Tüm uzakAPPENDveyaPREPENDyüklemeleri, devam etmeden önceREFRESHyüklemesinin başarılı olmasını bekler. - Yerel verilerin yenilenmesi gerekmediği durumlarda
initialize(),InitializeAction.SKIP_INITIAL_REFRESHdeğerini döndürmelidir. Bu durumdaRemoteMediator, uzaktan yenileme işlemini atlar ve önbelleğe alınmış verileri yükler.
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); }
Çağrı cihazı oluşturma
Son olarak, sayfalandırılmış veri akışını ayarlamak için bir Pager örneği oluşturmanız gerekir.
Bu, basit bir ağ veri kaynağından Pager oluşturmaya benzer ancak iki şeyi farklı yapmanız gerekir:
PagingSourceoluşturucusunu doğrudan iletmek yerine, DAO'danPagingSourcenesnesi döndüren sorgu yöntemini sağlamanız gerekir.RemoteMediatoruygulamanızın bir örneğiniremoteMediatorparametresi olarak sağlamalısınız.
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));
Uzaktan kumanda anahtarlarını yönetme
Uzak anahtarlar, bir RemoteMediator uygulamasının arka uç hizmetine hangi verilerin bir sonraki aşamada yükleneceğini bildirmek için kullandığı anahtarlardır. En basit durumda, sayfalandırılmış verilerin her öğesi, kolayca başvurabileceğiniz bir uzak anahtar içerir. Ancak uzaktan anahtarlar tek tek öğelere karşılık gelmiyorsa bunları ayrı ayrı saklamanız ve load() yönteminizde yönetmeniz gerekir.
Bu bölümde, tek tek öğelerde depolanmayan uzaktan kumanda anahtarlarının nasıl toplanacağı, depolanacağı ve güncelleneceği açıklanmaktadır.
Öğe anahtarları
Bu bölümde, tek tek öğelere karşılık gelen uzak anahtarlarla nasıl çalışılacağı açıklanmaktadır. Genellikle, bir API bağımsız öğelerden ayrıldığında öğe kimliği bir sorgu parametresi olarak iletilir. Parametre adı, sunucunun sağlanan kimlikten önceki veya sonraki öğelerle yanıt verip vermeyeceğini gösterir. User model sınıfı örneğinde, ek veri istenirken sunucudaki id alanı uzak anahtar olarak kullanılır.
load() yönteminizin öğeye özgü uzak anahtarları yönetmesi gerektiğinde bu anahtarlar genellikle sunucudan getirilen verilerin kimlikleridir. Yenileme işlemleri yalnızca en son verileri aldığından yükleme anahtarı gerektirmez.
Benzer şekilde, yenileme işlemi her zaman sunucudan en yeni verileri çektiği için ekleme işlemlerinin herhangi bir ek veri getirmesi gerekmez.
Ancak ekleme işlemleri için kimlik gerekir. Bu işlem için veritabanındaki son öğeyi yüklemeniz ve sonraki veri sayfasını yüklemek için bu öğenin kimliğini kullanmanız gerekir. Veritabanında öğe yoksa endOfPaginationReached değeri doğru (true) olarak ayarlanır. Bu, verilerin yenilenmesi gerektiğini gösterir.
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); }
Sayfa tuşları
Bu bölümde, tek tek öğelere karşılık gelmeyen uzak anahtarlarla nasıl çalışılacağı açıklanmaktadır.
Uzak anahtar tablosu ekleme
Uzak anahtarlar liste öğeleriyle doğrudan ilişkilendirilmediğinde, bunları yerel veritabanındaki ayrı bir tabloda saklamak en iyisidir. Uzak anahtarlar tablosunu temsil eden bir Varlık tanımlayın:
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; }
Ayrıca RemoteKey öğesi için bir DAO tanımlamanız gerekir:
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); }
Uzaktan anahtarlarla yükleme
load() yönteminizin uzak sayfa anahtarlarını yönetmesi gerektiğinde, RemoteMediator'ün temel kullanımına kıyasla yöntemi aşağıdaki şekillerde farklı tanımlamanız gerekir:
- Uzak anahtar tablonuz için DAO'ya referans içeren ek bir özellik ekleyin.
PagingStatekullanmak yerine uzak anahtar tablosunu sorgulayarak bir sonraki anahtarın hangisi olacağını belirleyin.- Ağ veri kaynağından döndürülen uzak anahtarı, sayfalandırılmış verilerin kendisinin yanı sıra ekleyin veya depolayın.
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); }
Ek kaynaklar
Paging kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:
Codelab uygulamaları
Örnekler
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Sayfalandırılmış verileri yükleme ve görüntüleme
- Paging uygulamanızı test etme
- Paging 3'e geçiş