Sorgen Sie für eine bessere Nutzererfahrung, indem Sie dafür sorgen, dass Ihre App auch dann verwendet werden kann, wenn die Netzwerkverbindungen unzuverlässig sind oder der Nutzer offline ist. Eine Möglichkeit besteht darin, gleichzeitig aus dem Netzwerk und aus einer lokalen Datenbank zu paginieren. Auf diese Weise ruft Ihre App die UI aus einem lokalen Datenbankcache ab und sendet nur dann Anfragen an das Netzwerk, wenn keine Daten mehr in der Datenbank vorhanden sind.
In diesem Leitfaden wird davon ausgegangen, dass Sie mit der Room-Persistenz bibliothek und der grundlegenden Verwendung der Paging bibliothek vertraut sind.
Datenladevorgänge koordinieren
Die Paging-Bibliothek stellt für diesen Anwendungsfall die
RemoteMediator Komponente
bereit. RemoteMediator fungiert als Signal der Paging-Bibliothek, wenn der App die Daten im Cache ausgehen. Sie können dieses Signal verwenden, um
zusätzliche Daten aus dem Netzwerk zu laden und in der lokalen Datenbank zu speichern, wo sie von einer
PagingSource geladen und der UI zur Anzeige
bereitgestellt werden können.
Wenn zusätzliche Daten benötigt werden, ruft die Paging-Bibliothek die
load() Methode aus
der RemoteMediator Implementierung auf. Dies ist eine aussetzende Funktion, sodass lange Vorgänge sicher ausgeführt werden können. Diese Funktion ruft in der Regel die neuen Daten aus einer Netzwerkquelle ab und speichert sie im lokalen Speicher.
Dieser Vorgang funktioniert mit neuen Daten. Im Laufe der Zeit müssen die in der Datenbank gespeicherten Daten jedoch ungültig gemacht werden, z. B. wenn der Nutzer manuell eine Aktualisierung auslöst. Dies
wird durch die LoadType
Eigenschaft dargestellt, die an die load() Methode übergeben wird. LoadType informiert den RemoteMediator darüber, ob die vorhandenen Daten aktualisiert oder zusätzliche Daten abgerufen werden müssen, die an die vorhandene Liste angehängt oder vorangestellt werden müssen.
Auf diese Weise sorgt der RemoteMediator dafür, dass Ihre App die Daten, die Nutzer sehen möchten, in der richtigen Reihenfolge lädt.
Lebenszyklus der Paginierung
Bei der Paginierung direkt aus dem Netzwerk lädt die PagingSource die Daten und
gibt ein
LoadResult
Objekt zurück. Die PagingSource Implementierung wird über den
Pager Parameter an den
pagingSourceFactory übergeben.
Wenn die UI neue Daten benötigt, ruft der Pager die
load() Methode aus der
PagingSource auf und gibt einen Stream von
PagingData Objekten zurück, die
die neuen Daten kapseln. Jedes PagingData-Objekt wird in der Regel im ViewModel im Cache gespeichert, bevor es zur Anzeige an die UI gesendet wird.
RemoteMediator ändert diesen Datenfluss. Eine PagingSource lädt weiterhin die Daten. Wenn die paginierten Daten jedoch erschöpft sind, löst die Paging-Bibliothek den RemoteMediator aus, um neue Daten aus der Netzwerkquelle zu laden. Der RemoteMediator speichert die neuen Daten in der lokalen Datenbank, sodass ein In-Memory-Cache im ViewModel nicht erforderlich ist. Schließlich macht sich die PagingSource selbst ungültig und der Pager erstellt eine neue Instanz, um die neuen Daten aus der Datenbank zu laden.
Grundlegende Nutzung
Angenommen, Ihre App soll Seiten mit User-Elementen aus einer nach Elementen sortierten Netzwerkdatenquelle in einen lokalen Cache laden, der in einer Room-Datenbank gespeichert ist.
Eine RemoteMediator-Implementierung hilft, paginierte Daten aus dem Netzwerk in die Datenbank zu laden, lädt aber keine Daten direkt in die UI. Stattdessen verwendet die App
die Datenbank als die Single Source of
Truth. Mit anderen Worten: Die App zeigt nur Daten an, die in der Datenbank im Cache gespeichert wurden. Eine PagingSource-Implementierung (z. B. eine von Room generierte) lädt die Daten aus dem Cache aus der Datenbank in die UI.
Room-Entitäten erstellen
Im ersten Schritt definieren Sie mit der Room-Persistenzbibliothek
eine Datenbank, die einen
lokalen Cache mit paginierten Daten aus der Netzwerkdatenquelle enthält. Beginnen Sie mit einer
Implementierung von RoomDatabase
, wie unter Daten in einer lokalen Datenbank mit
Room speichern beschrieben.
Definieren Sie als Nächstes eine Room-Entität, die eine Tabelle mit Listenelementen darstellt, wie unter
Daten mit Room-Entitäten definieren beschrieben.
Geben Sie ein id-Feld als Primärschlüssel sowie Felder für alle anderen Informationen an, die Ihre Listenelemente enthalten.
@Entity(tableName = "users") data class User(val id: String, val label: String)
Sie müssen auch ein Data Access Object (DAO) für diese Room-Entität definieren, wie unter Auf Daten mit Room DAOs zugreifen beschrieben. Das DAO für die Listenelemententität muss die folgenden Methoden enthalten:
- Eine
insertAll()-Methode, mit der eine Liste von Elementen in die Tabelle eingefügt wird. - Eine Methode, die den Abfragestring als Parameter verwendet und ein
PagingSource-Objekt für die Liste der Ergebnisse zurückgibt. Auf diese Weise kann einPager-Objekt diese Tabelle als Quelle für paginierte Daten verwenden. - Eine
clearAll()-Methode, mit der alle Daten der Tabelle gelöscht werden.
@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() }
RemoteMediator implementieren
Die Hauptaufgabe von RemoteMediator besteht darin, weitere Daten aus dem Netzwerk zu laden, wenn dem Pager die Daten ausgehen oder die vorhandenen Daten ungültig gemacht werden. Es enthält eine load()-Methode, die Sie überschreiben müssen, um das Ladeverhalten zu definieren.
Eine typische RemoteMediator-Implementierung enthält die folgenden Parameter:
query: Ein Abfragestring, der definiert, welche Daten aus dem Back-End-Dienst abgerufen werden sollen.database: Die Room-Datenbank, die als lokaler Cache dient.networkService: Eine API-Instanz für den Backend-Dienst.
Erstellen Sie eine RemoteMediator<Key, Value>-Implementierung. Die Typen Key und Value sollten dieselben sein wie bei der Definition einer PagingSource für dieselbe Netzwerkdatenquelle. Weitere Informationen zum
Auswählen von Typparametern finden Sie unter Schlüssel- und Wert
typenauswählen.
@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 { // ... } }
Die Methode load() ist für das Aktualisieren des zugrunde liegenden Datasets und das Ungültigmachen der PagingSource verantwortlich. Einige Bibliotheken, die die Paginierung unterstützen (z. B. Room), machen die PagingSource-Objekte, die sie implementieren, automatisch ungültig.
Die Methode load() verwendet zwei Parameter:
PagingStateenthält Informationen zu den bisher geladenen Seiten, dem zuletzt aufgerufenen Index und demPagingConfig-Objekt, mit dem Sie den Paginierungsstream initialisiert haben.LoadTypegibt den Typ des Ladevorgangs an:REFRESH,APPENDoderPREPEND.
Der Rückgabewert der load() Methode ist ein
MediatorResult
Objekt. MediatorResult kann entweder
MediatorResult.Error
(mit der Fehlerbeschreibung) oder
MediatorResult.Success
(mit einem Signal, das angibt, ob weitere Daten geladen werden müssen) sein.
Die Methode load() muss die folgenden Schritte ausführen:
- Bestimmen Sie anhand des Ladetyps und der bisher geladenen Daten, welche Seite aus dem Netzwerk geladen werden soll.
- Lösen Sie die Netzwerkanfrage aus.
- Führen Sie je nach Ergebnis des Ladevorgangs Aktionen aus:
- Wenn der Ladevorgang erfolgreich ist und die empfangene Liste von Elementen nicht leer ist, speichern Sie die Listenelemente in der Datenbank und geben Sie
MediatorResult.Success(endOfPaginationReached = false)zurück. Nachdem die Daten gespeichert wurden, machen Sie die Datenquelle ungültig, um die Paging-Bibliothek über die neuen Daten zu informieren. - Wenn der Ladevorgang erfolgreich ist und die empfangene Liste von Elementen leer ist oder es sich um den letzten Seitenindex handelt, geben Sie
MediatorResult.Success(endOfPaginationReached = true)zurück. Nachdem die Daten gespeichert wurden, machen Sie die Datenquelle ungültig, um die Paging-Bibliothek über die neuen Daten zu informieren. - Wenn die Anfrage einen Fehler verursacht, geben Sie
MediatorResult.Errorzurück.
- Wenn der Ladevorgang erfolgreich ist und die empfangene Liste von Elementen nicht leer ist, speichern Sie die Listenelemente in der Datenbank und geben Sie
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) } }
Methode „initialize“ definieren
RemoteMediator-Implementierungen können auch die
initialize()
Methode überschreiben, um zu prüfen, ob die Daten im Cache veraltet sind, und zu entscheiden, ob
eine Remote-Aktualisierung ausgelöst werden soll. Diese Methode wird vor jedem Ladevorgang ausgeführt, sodass Sie die Datenbank bearbeiten können (z. B. alte Daten löschen), bevor Sie lokale oder Remote-Ladevorgänge auslösen.
Da initialize() eine asynchrone Funktion ist, können Sie Daten laden, um die Relevanz der vorhandenen Daten in der Datenbank zu bestimmen. In den meisten Fällen sind die Daten im Cache nur für einen bestimmten Zeitraum gültig. Der RemoteMediator kann prüfen, ob dieser Ablaufzeitpunkt überschritten wurde. In diesem Fall muss die Paging-Bibliothek die Daten vollständig aktualisieren. Implementierungen von initialize() sollten eine InitializeAction wie folgt zurückgeben:
- Wenn die lokalen Daten vollständig aktualisiert werden müssen,
initialize()sollte zurückgebenInitializeAction.LAUNCH_INITIAL_REFRESH. Dadurch führt derRemoteMediatoreine Remote-Aktualisierung durch, um die Daten vollständig neu zu laden. Alle Remote-Ladevorgänge vom TypAPPENDoderPREPENDwarten, bis der Ladevorgang vom TypREFRESHerfolgreich abgeschlossen wurde. - Wenn die lokalen Daten nicht aktualisiert werden müssen,
initialize()sollte zurückgebenInitializeAction.SKIP_INITIAL_REFRESH. Dadurch überspringt derRemoteMediatordie Remote-Aktualisierung und lädt die Daten aus dem Cache.
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 } }
Pager erstellen
Schließlich müssen Sie eine Pager-Instanz erstellen, um den Stream der paginierten Daten einzurichten.
Dies ähnelt dem Erstellen eines Pager aus einer einfachen Netzwerkdatenquelle. Es gibt jedoch zwei Dinge, die Sie anders machen müssen:
- Anstatt einen
PagingSource-Konstruktor direkt zu übergeben, müssen Sie die Abfragemethode angeben, die einPagingSource-Objekt aus dem DAO zurückgibt. - Sie müssen eine Instanz Ihrer
RemoteMediator-Implementierung als ParameterremoteMediatorangeben.
val userDao = database.userDao() val pager = Pager( config = PagingConfig(pageSize = 50) remoteMediator = ExampleRemoteMediator(query, database, networkService) ) { userDao.pagingSource(query) }
Race-Bedingungen verarbeiten
Eine Situation, die Ihre App beim Laden von Daten aus mehreren Quellen verarbeiten muss, ist der Fall, in dem die lokalen im Cache gespeicherten Daten nicht mehr mit der Remote-Datenquelle synchronisiert sind.
Wenn die Methode initialize() aus Ihrer RemoteMediator-Implementierung LAUNCH_INITIAL_REFRESH zurückgibt, sind die Daten veraltet und müssen durch neue Daten ersetzt werden. Alle Ladeanfragen vom Typ PREPEND oder APPEND müssen warten, bis der Remote-Ladevorgang vom Typ REFRESH erfolgreich abgeschlossen wurde. Da die Anfragen vom Typ PREPEND oder APPEND vor der Anfrage vom Typ REFRESH in die Warteschlange gestellt wurden, ist es möglich, dass der an diese Ladevorgänge übergebene PagingState veraltet ist, wenn sie ausgeführt werden.
Je nachdem, wie die Daten lokal gespeichert werden, kann Ihre App redundante Anfragen ignorieren, wenn Änderungen an den Daten im Cache zu einer Ungültigmachung und neuen Datenabrufen führen.
Room macht beispielsweise Abfragen bei jedem Einfügen von Daten ungültig. Das bedeutet, dass bei neuen Daten, die in die Datenbank eingefügt werden, neue PagingSource-Objekte mit den aktualisierten Daten für ausstehende Ladeanfragen bereitgestellt werden.
Die Lösung dieses Problems der Datensynchronisierung ist entscheidend, um sicherzustellen, dass Nutzer die relevantesten und aktuellsten Daten sehen. Die beste Lösung hängt hauptsächlich davon ab, wie die Netzwerkdatenquelle die Daten paginiert. In jedem Fall können Sie mit Remote Schlüsseln Informationen zur zuletzt vom Server angeforderten Seite speichern. Ihre App kann diese Informationen verwenden, um die richtige Datenseite zu identifizieren und anzufordern, die als Nächstes geladen werden soll.
Fernbedienungen verwalten
Remote-Schlüssel sind Schlüssel, die eine RemoteMediator-Implementierung verwendet, um dem Backend-Dienst mitzuteilen, welche Daten als Nächstes geladen werden sollen. Im einfachsten Fall enthält jedes Element der paginierten Daten einen Remote-Schlüssel, auf den Sie leicht verweisen können. Wenn die Remote-Schlüssel jedoch nicht einzelnen Elementen entsprechen, müssen Sie sie separat speichern und in Ihrer Methode load() verwalten.
In diesem Abschnitt wird beschrieben, wie Sie Remote-Schlüssel erfassen, speichern und aktualisieren, die nicht in einzelnen Elementen gespeichert sind.
Element-Schlüssel
In diesem Abschnitt wird beschrieben, wie Sie mit Remote-Schlüsseln arbeiten, die einzelnen Elementen entsprechen. Wenn eine API Schlüssel für einzelne Elemente verwendet, wird die Element-ID in der Regel als Abfrageparameter übergeben. Der Parametername gibt an, ob der Server mit Elementen vor oder nach der angegebenen ID antworten soll. Im Beispiel der User-Modellklasse wird das Feld id vom Server als Remote-Schlüssel verwendet, wenn zusätzliche Daten angefordert werden.
Wenn Ihre Methode load() elementspezifische Remote-Schlüssel verwalten muss, sind diese Schlüssel in der Regel die IDs der vom Server abgerufenen Daten. Für Aktualisierungsvorgänge ist kein Ladeschlüssel erforderlich, da nur die neuesten Daten abgerufen werden.
Ebenso müssen bei Vorgängen vom Typ „prepend“ keine zusätzlichen Daten abgerufen werden, da bei der Aktualisierung immer die neuesten Daten vom Server abgerufen werden.
Für Vorgänge vom Typ „append“ ist jedoch eine ID erforderlich. Dazu müssen Sie das letzte Element aus der Datenbank laden und seine ID verwenden, um die nächste Datenseite zu laden. Wenn keine Elemente in der Datenbank vorhanden sind, wird endOfPaginationReached auf „true“ gesetzt, was darauf hinweist, dass eine Datenaktualisierung erforderlich ist.
@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) } } }
Seitenschlüssel
In diesem Abschnitt wird beschrieben, wie Sie mit Remote-Schlüsseln arbeiten, die nicht einzelnen Elementen entsprechen.
Tabelle mit Remote-Schlüsseln hinzufügen
Wenn Remote-Schlüssel nicht direkt mit Listenelementen verknüpft sind, sollten Sie sie in einer separaten Tabelle in der lokalen Datenbank speichern. Definieren Sie eine Room-Entität, die eine Tabelle mit Remote-Schlüsseln darstellt:
@Entity(tableName = "remote_keys") data class RemoteKey(val label: String, val nextKey: String?)
Sie müssen auch ein DAO für die RemoteKey-Entität definieren:
@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) }
Mit Remote-Schlüsseln laden
Wenn Ihre load() Methode Remote-Seitenschlüssel verwalten muss, müssen Sie sie im Vergleich zur grundlegenden Verwendung von RemoteMediator anders definieren:
- Fügen Sie eine zusätzliche Eigenschaft hinzu, die einen Verweis auf das DAO für Ihre Tabelle mit Remote-Schlüsseln enthält.
- Bestimmen Sie, welcher Schlüssel als Nächstes geladen werden soll, indem Sie die Tabelle mit Remote-Schlüsseln abfragen, anstatt
PagingStatezu verwenden. - Fügen Sie den zurückgegebenen Remote-Schlüssel aus der Netzwerkdatenquelle zusätzlich zu den paginierten Daten selbst ein oder speichern Sie ihn.
@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) } } }
Vor Ort aktualisieren
Wenn Ihre App nur Netzwerkaktualisierungen von oben in der Liste unterstützen muss, wie in den vorherigen Beispielen, muss Ihr RemoteMediator kein Ladeverhalten vom Typ „prepend“ definieren.
Wenn Ihre App jedoch das inkrementelle Laden aus dem Netzwerk in die lokale Datenbank unterstützen muss, müssen Sie die Fortsetzung der Paginierung ab dem Ankerpunkt, der Scrollposition des Nutzers, unterstützen. Die PagingSource
Implementierung von Room übernimmt das für Sie. Wenn Sie Room nicht verwenden, können Sie dies tun, indem Sie
dies tun, indem Sie
PagingSource.getRefreshKey() überschreiben.
Ein Beispiel für die Implementierung von getRefreshKey() finden Sie unter Define the
PagingSource.
Abbildung 2 veranschaulicht den Vorgang des Ladens von Daten zuerst aus der lokalen Datenbank und dann aus dem Netzwerk, sobald die Datenbank keine Daten mehr enthält.
Zusätzliche Ressourcen
Weitere Informationen zur Paging-Bibliothek finden Sie in den folgenden zusätzlichen Ressourcen:
Inhalte ansehen
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Paginierte Daten laden und anzeigen
- Paging-Implementierung testen
- Zu Paging 3 migrieren