Uygulamanızın kullanılabilmesini sağlayarak daha iyi bir kullanıcı deneyimi sağlayın ağ bağlantıları güvenilir olmadığında veya kullanıcı çevrimdışı olduğunda. Paydaşların ihtiyaçlarını aynı anda ağdan ve yerel bir veritabanından sayfa oluşturmak için kullanılır. Bu şekilde, uygulamanız kullanıcı arayüzünü yerel veritabanı önbelleğinden alır ve yeni bir istek gönderir.
Bu kılavuzda, Odada devamlılık hakkında bilgi sahibi olduğunuz varsayılır. kitaplığını ve Sayfalama sekmesinin temel kullanımını kitaplığı.
Veri yüklemelerini koordine etme
Sayfalandırma kitaplığı
RemoteMediator
bileşeni
e-tablo kullanmaktır. RemoteMediator
, Çağrı kitaplığından sinyal görevi görür
Uygulamada önbelleğe alınan veriler kalmadığında. Web sitenizdeki öğeleri yüklemek için
ağdan ek veriler toplar ve bu verileri yerel veritabanında saklar. Burada bir
PagingSource
bunu yükleyebilir ve
bunu, görüntülenecek kullanıcı arayüzüne sağlayın.
Ek veri gerektiğinde, Sayfalama kitaplığı
load()
yöntemi, kaynak:
RemoteMediator
uygulanması. Bu, güvenli olduğu için bir askıya alma işlevidir
bir ekip olarak düşünebilirsiniz. Bu işlev genellikle yeni verileri
ve bunu yerel depolama alanına kaydeder.
Bu işlem yeni verilerle çalışır ancak zaman içinde veritabanında depolanan veriler
Örneğin, kullanıcının manuel olarak yenilemeyi tetiklemesi gibi durumlarda geçersiz kılma işlemi gerekir. Bu
LoadType
ile temsil edilir
özelliği, load()
yöntemine iletildi. LoadType
,
RemoteMediator
: Mevcut verileri yenilemenin mi yoksa getirme mi gerekli?
mevcut listenin başına veya sonuna eklenmesi gereken ek veriler.
Bu şekilde RemoteMediator
, uygulamanızın
uygun sırada görüntülemek istediklerini belirtir.
Sayfalandırma yaşam döngüsü
PagingSource
, doğrudan ağdan sayfalama yaparken verileri yükler ve
şunu döndürür:
LoadResult
nesnesini tanımlayın. PagingSource
uygulaması
Pager
-
pagingSourceFactory
parametresi.
Kullanıcı arayüzü tarafından yeni veriler gerektiği için Pager
,
load()
yöntemini kullanarak
PagingSource
ve şunu akışı olarak döndürür:
PagingData
nesne
yeni verileri kapsamalıdır. Her PagingData
nesnesi genellikle
Gösterilecek kullanıcı arayüzüne gönderilmeden önce ViewModel
.
RemoteMediator
bu veri akışını değiştirir. PagingSource
, verileri yüklemeye devam eder;
ancak sayfalandırılmış veriler tükendiğinde, Sayfalama kitaplığı
Ağ kaynağından yeni veri yüklemek için RemoteMediator
. RemoteMediator
yeni verileri yerel veritabanında saklar. Böylece,
ViewModel
gerekli değil. Son olarak PagingSource
kendini 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 içeren veya öğeler içeren bir reklamdan User
öğe içeren sayfaları yüklemesini istediğinizi varsayalım
ağ veri kaynağını Oda veritabanında depolanan yerel bir önbelleğe aktarmanızı sağlar.
RemoteMediator
uygulaması, sayfalandırılmış verilerin ağdan
ancak verileri doğrudan kullanıcı arayüzüne yüklemez. Bunun yerine, uygulama
veri kaynağı olarak
doğru. Yani uygulama yalnızca
veritabanında önbelleğe alınan verileri gösterir. PagingSource
uygulama (örneğin, Oda tarafından oluşturulan bir uygulama) önbelleğe alınan verileri yüklemeyi işler
veri tabanından arayüze aktarmasını sağlar.
Oda varlıkları oluşturma
İlk adım, Odada kalıcılık
kitaplığını depolayan bir veritabanı
ağ veri kaynağındaki sayfalandırılmış verilerin yerel önbelleğidir. Statik web sitelerini
RoomDatabase
uygulaması
Aşağıdakileri kullanarak verileri yerel veritabanına kaydedin:
Oda.
Ardından, aşağıda açıklandığı gibi liste öğelerinden oluşan bir tablo temsil edecek bir Room varlığı tanımlayın
Oda varlıklarını kullanarak veri tanımlama.
Birincil anahtar olarak bir id
alanı ve
bilgi edinin.
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; }
Ayrıca bu Oda varlığı için şu şekilde bir veri erişim nesnesi (DAO) tanımlamanız gerekir: Odayı kullanarak verilere erişme DAO'lar. Liste öğesinin DAO'su varlık aşağıdaki yöntemleri içermelidir:
- Tabloya öğe listesi ekleyen
insertAll()
yöntemidir. - Sorgu dizesini parametre olarak alan ve
Sonuç listesi için
PagingSource
nesnesi. Bu şekilde, birPager
nesnesi bu tabloyu sayfalı veri kaynağı olarak kullanın. - Tablodaki tüm verileri silen bir
clearAll()
yöntemi.
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(); }
Bir RemoteMediator uygulayın
RemoteMediator
ana rolü, aşağıdaki durumlarda ağdan daha fazla veri yüklemektir:
ya Pager
verileri biter ya da mevcut veriler geçersiz kılınır. Google
Yüklemeyi tanımlamak için geçersiz kılmanız gereken bir load()
yöntemi içerir
gösterir.
Tipik bir RemoteMediator
uygulaması aşağıdaki parametreleri içerir:
query
: Arka uçtan hangi verilerin alınacağını tanımlayan bir sorgu dizesi geliştirmenizi sağlar.database
: Yerel önbellek işlevi gören Oda veritabanı.networkService
: Arka uç hizmeti için bir API örneği.
Bir RemoteMediator<Key, Value>
uygulaması oluşturun. Key
türü ve
Value
türü,
PagingSource
(aynı ağ veri kaynağı için). Daha fazla bilgi için
Tür parametrelerini seçmek için Anahtar ve değer seçme
türler olarak tanımlar.
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()
yöntemi, yedekleme veri kümesini güncellemekten ve
PagingSource
öğesi geçersiz kılınıyor. Sayfalara ayırmayı destekleyen bazı kitaplıklar (ör. Oda)
yapılandırdıkları PagingSource
nesneyi otomatik olarak geçersiz kılacak.
yardımcı olur.
load()
yöntemi iki parametre alır:
- Şunları içeren
PagingState
: şimdiye kadar yüklenen sayfalar, en son erişilen dizin, vePagingConfig
nesnesini tanımlayın. LoadType
, bu değer Yük türü:REFRESH
,APPEND
veyaPREPEND
load()
yönteminin döndürülen değeri
MediatorResult
nesnesini tanımlayın. MediatorResult
şunlardan biri olabilir:
MediatorResult.Error
(hata açıklamasını içerir) veya
MediatorResult.Success
(Yüklenecek daha fazla veri olup olmadığını belirten bir sinyal içerir).
load()
yöntemi, aşağıdaki adımları gerçekleştirmelidir:
- Bağlantının yüküne ve şekline bağlı olarak ağdan hangi sayfanın yükleneceğini belirleyin yüklenen verilerdir.
- Ağ isteğini tetikleyin.
- Yükleme işleminin sonucuna göre işlemleri gerçekleştirin:
- Yükleme başarılı olursa ve alınan öğeler listesi boş değilse
liste öğelerini veritabanında depolayıp
MediatorResult.Success(endOfPaginationReached = false)
Verilerden sonra öğesinin Sayfalama kitaplığını bilgilendirmek için veri kaynağını geçersiz kılın yeni veriler oluşturabilirsiniz. - Yükleme başarılıysa ve alınan öğe listesi boşsa
olursa, o son sayfa dizini ise
MediatorResult.Success(endOfPaginationReached = true)
Veriler başladıktan sonra yeni tarayıcının Sayfalama kitaplığını bildirmek için veri kaynağını geçersiz kılın verileri. - İstek bir hataya neden olursa
MediatorResult.Error
değerini döndürün.
- Yükleme başarılı olursa ve alınan öğeler listesi boş değilse
liste öğelerini veritabanında depolayıp
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 ); }
İlk kullanıma hazırlama yöntemini tanımlama
RemoteMediator
uygulamaları,
initialize()
yöntemini kullanarak önbelleğe alınan verilerin güncel olup olmadığını kontrol edebilir ve
uzaktan yenilemenizi sağlar. Bu yöntem herhangi bir yükleme yapılmadan önce çalışır, böylece
herhangi birini tetiklemeden önce veritabanını değiştirmek (örneğin, eski verileri temizlemek için)
yerel veya uzak yüklemeler.
initialize()
eşzamansız bir fonksiyon olduğundan,
Veri tabanındaki mevcut verilerin alaka düzeyini belirlemek. En yaygın
önbelleğe alınan veriler yalnızca belirli bir süre için geçerli olur. İlgili içeriği oluşturmak için kullanılan
RemoteMediator
, geçerlilik süresinin dolup dolmadığını kontrol edebilir.
Bu durumda, Sayfalama kitaplığının verileri tamamen yenilemesi gerekir. Kullanım alanları
initialize()
, şu şekilde bir InitializeAction
döndürmelidir:
- Yerel verilerin tamamen yenilenmesinin gerektiği durumlarda
initialize()
dönmelidirInitializeAction.LAUNCH_INITIAL_REFRESH
Bu durum,RemoteMediator
ürününün tamamen yeniden yüklemek için uzaktan yenileme yapmasına neden olur. bahsedeceğim. UzakAPPEND
veyaPREPEND
yüklemeleriREFRESH
yüklemesini bekler adım adım başarılı olmanızı sağlar. - Yerel verilerin yenilenmesinin gerekmediği durumlarda
initialize()
dönmelidirInitializeAction.SKIP_INITIAL_REFRESH
Bu,RemoteMediator
ürününün uzaktan yenilemeyi atlayıp önbelleğe alınmış verileri görebilirsiniz.
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); }
Ç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
farklı şekilde yapmanız gereken iki şey var:
- Doğrudan bir
PagingSource
oluşturucuyu iletmek yerine, DAO'dan birPagingSource
nesnesi döndüren sorgu yöntemi. RemoteMediator
uygulamanızın bir örneğiniremoteMediator
parametresinden yararlanın.
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));
Yarış koşullarını kontrol edin
Uygulamanızın, birden çok cihazdan veri yüklerken ilgilenmesi gereken kaynaklar, önbelleğe alınmış yerel verilerin uzak veri kaynağı.
RemoteMediator
uygulamanızdaki initialize()
yöntemi döndüğünde
LAUNCH_INITIAL_REFRESH
, veriler güncel değil ve yenileriyle değiştirilmeleri gerekiyor
verileri. PREPEND
veya APPEND
yükleme istekleri, uzaktan kumandayı beklemeye zorlanır
Başarıyla tamamlanması için REFRESH
yükleme. PREPEND
veya APPEND
talepleri
REFRESH
isteğinden önce sıraya alındığında, PagingState
yüklendikleri zamana kadar güncelliğini yitirir.
Verilerin yerel olarak nasıl depolandığına bağlı olarak uygulamanız gereksiz verileri yoksayabilir
istekleri için geçerlidir.
Örneğin Oda, veri eklemedeki sorguları geçersiz kılar. Bu durumda, yeni
Yenilenen verilere sahip PagingSource
nesne, bekleyen yüklemeye verilir
istekleri için kullanılır.
Bu veri senkronizasyonu sorununun çözülmesi, kullanıcıların web sitenizdeki en alakalı ve en güncel verileri görebilirsiniz. En iyi çözüm büyük ölçüde Bu işlem, ağ veri kaynağının verileri sayfalama şeklidir. Her durumda, uzaktan tuşları kullanarak en son sayfayla ilgili bilgileri kaydedebilirsiniz için sunucu tarafından istekte bulunuldu. Uygulamanız bu bilgileri kullanarak yüklenmesi için doğru veri sayfasını isteme.
Uzaktan tuşları yönetin
Uzaktan tuşlar, bir RemoteMediator
uygulamasının kullanımı algılamak için kullandığı anahtarlardır
arka uç hizmetinin nasıl yükleneceğini belirleyin. En basit senaryoda,
sayfalandırılmış veriler, kolayca başvurabileceğiniz bir uzak anahtar içerir. Ancak,
uzaktaki tuşlar tek tek öğelere karşılık gelmez, bu durumda bunları saklamanız gerekir
ve bunları load()
yönteminizde yönetebilirsiniz.
Bu bölümde, uzaktan kumanda olarak kullanılan uzaktan kumandaların nasıl toplanacağı, depolanacağı ve güncelleneceği ayrı öğelerde depolanmaz.
Öğe anahtarları
Bu bölümde, uzaktan erişim için uzaktaki
öğe oluşturabilirsiniz. Genellikle bir API anahtarı tek tek öğelerin devre dışı bırakıldığında
Kimlik, sorgu parametresi olarak iletilir. Parametre adı,
sunucusu, sağlanan kimlikten önce veya sonra öğelerle yanıt vermelidir. Örnekte
User
model sınıfı için sunucudaki id
alanı, uzaktan kumanda olarak kullanılır.
tuşuna basın.
load()
yönteminizle öğeye özel uzaktan tuşların yönetilmesi gerektiğinde bu tuşlar
genellikle sunucudan getirilen verilerin kimlikleridir. Yenileme işlemleri
en yeni verileri aldıklarından yükleme anahtarına ihtiyaç duymazlar.
Benzer şekilde, başına ekleme işlemlerinin herhangi bir ek veri getirmesine gerek yoktur çünkü
yenileme her zaman sunucudan en yeni verileri çeker.
Ancak, ekleme işlemleri kimlik gerektirir. Bu işlem, her bir sayfa için en son
öğesini kaldırın ve sonraki veri sayfasını yüklemek için kimliğini kullanın. Varsa
veritabanında öğe yoksa endOfPaginationReached
true olarak ayarlanır.
Bu gösterge, verilerin yenilenmesi gerektiğini gösterir.
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); }
Sayfa anahtarları
Bu bölümde, öğe oluşturabilirsiniz.
Uzak anahtar tablosu ekle
Uzak tuşlar liste öğeleriyle doğrudan ilişkilendirilmediğinde bunları yerel veritabanında ayrı bir tabloda saklamanız gerekir. Aşağıdaki özelliklere sahip bir Room varlığı tanımlayın: uzak tuşların bir tablosunu temsil eder:
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; }
Ayrıca, RemoteKey
varlığı için bir DAO tanımlamanız gerekir:
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); }
Uzaktan anahtarlarla yükle
load()
yönteminizin uzak sayfa anahtarlarını yönetmesi gerektiğinde bunu tanımlamanız gerekir
temel kullanımına kıyasla şu farklılıklara sahip
RemoteMediator
:
- Sizin için DAO'ya referans veren ek bir mülk ekleyin: uzaktan tuş tablosunu kullanın.
- Bunun yerine uzak anahtar tablosunu sorgulayarak bir sonraki anahtarın yükleneceğini belirleyin
PagingState
kullanılıyor. - Ağ veri kaynağından döndürülen uzak anahtarı ek olarak sunar.
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); }
Yerinde yenile
Uygulamanızın yalnızca listenin üst kısmından ağ yenilemelerini desteklemesi gerekiyorsa
olduğu durumlarda, RemoteMediator
öğesinin
başa yükleme davranışını ekleyin.
Ancak uygulamanızın ağdan artımlı yüklemeyi desteklemesi gerekiyorsa
sonra sayfalandırma işlemini devam ettirmek için destek sağlamanız gerekir.
kullanıcının kaydırma konumunda. Odanın PagingSource
cihazı
uygulama bunu sizin için halleder, ancak Odayı kullanmıyorsanız
geçersiz kılarak bunu
PagingSource.getRefreshKey()
getRefreshKey()
ile ilgili bir örnek uygulama için
Sayfa Kaynağı.
Şekil 4'te, öncelikle yerel veritabanından veri yükleme işlemi gösterilmektedir. ve veritabanındaki veriler tükendiğinde ağdan veri gönderir.
Ek kaynaklar
Sayfalama kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:
Codelab'ler
Örnekler
ziyaret edin.Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Sayfalandırılmış verileri yükleme ve görüntüleme
- Sayfalama uygulamanızı test etme
- Sayfa 3'e geçme