Seitendaten laden und anzeigen

Die Paging-Bibliothek bietet leistungsstarke Funktionen zum Laden und Anzeigen von Seiten mit Daten aus einem größeren Dataset. In dieser Anleitung wird gezeigt, wie Sie mit der Paging Bibliothek einen Stream mit Seiten mit Daten aus einer Netzwerkdatenquelle einrichten und anzeigen in einer Lazy List.

Datenquelle definieren

Im ersten Schritt definieren Sie eine PagingSource-Implementierung, um die Datenquelle zu identifizieren. Die PagingSource API-Klasse enthält die load-Methode, die Sie überschreiben, um anzugeben, wie Seiten mit Daten aus der entsprechenden Datenquelle abgerufen werden.

Verwenden Sie die Klasse PagingSource direkt, um Kotlin-Coroutinen für das asynchrone Laden zu verwenden.

Schlüssel- und Werttypen auswählen

PagingSource<Key, Value> hat zwei Typparameter: Key und Value. Der Schlüssel definiert die ID, die zum Laden der Daten verwendet wird, und der Wert ist der Typ der Daten selbst. Wenn Sie beispielsweise Seiten mit User-Objekten aus dem Netzwerk laden, indem Sie Int-Seitenzahlen an Retrofit übergeben, wählen Sie Int als Key-Typ und User als Value-Typ aus.

PagingSource definieren

Im folgenden Beispiel wird eine PagingSource implementiert, die Seiten mit Elementen anhand der Seitenzahl lädt. Der Key-Typ ist Int und der Value-Typ ist User.

class ExamplePagingSource(
    val backend: ExampleBackendService,
    val query: String
) : PagingSource<Int, User>() {
  override suspend fun load(
    params: LoadParams<Int>
  ): LoadResult<Int, User> {

    init {
        // the data source is expected to be immutable
        // invalidate PagingSource if data source
        // has updated
        backEnd.addDatabaseOnChangedListener {
            invalidate()
        }
    }

    try {
      // Start refresh at page 1 if undefined.
      val nextPageNumber = params.key ?: 1
      val response = backend.searchUsers(query, nextPageNumber)
      return LoadResult.Page(
        data = response.users,
        prevKey = null, // Only paging forward.
        nextKey = nextPageNumber + 1
      )
    } catch (e: Exception) {
      // Handle errors in this block and return LoadResult.Error for
      // expected errors (such as a network failure).
    }
  }

  override fun getRefreshKey(state: PagingState<Int, User>): Int? {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    return state.anchorPosition?.let { anchorPosition ->
      val anchorPage = state.closestPageToPosition(anchorPosition)
      anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
    }
  }
}

Eine typische PagingSource-Implementierung übergibt Parameter, die im Konstruktor angegeben sind, an die Methode load, um die entsprechenden Daten für eine Abfrage zu laden. Im obigen Beispiel sind das folgende Parameter:

  • backend: eine Instanz des Backend-Dienstes, der die Daten bereitstellt
  • query: die Suchanfrage, die an den Dienst gesendet werden soll, der durch backend angegeben wird

Das LoadParams Objekt enthält Informationen zum auszuführenden Ladevorgang. Dazu gehören der zu ladende Schlüssel und die Anzahl der zu ladenden Elemente.

Das LoadResult Objekt enthält das Ergebnis des Ladevorgangs. LoadResult ist eine versiegelte Klasse, die je nachdem, ob der load-Aufruf erfolgreich war, eine von drei Formen annehmen kann:

  • Wenn das Laden erfolgreich ist, geben Sie ein LoadResult.Page-Objekt zurück.
  • Wenn das Laden nicht erfolgreich ist, geben Sie ein LoadResult.Error-Objekt zurück.
  • Wenn die PagingSource nicht mehr gültig ist und durch eine neue Instanz ersetzt werden sollte (z. B. aufgrund einer zugrunde liegenden Datenänderung), geben Sie ein LoadResult.Invalid-Objekt zurück.

Die folgende Abbildung zeigt, wie die Funktion load in diesem Beispiel den Schlüssel für jeden Ladevorgang empfängt und den Schlüssel für den nachfolgenden Ladevorgang bereitstellt.

Bei jedem Ladeaufruf verwendet die ExamplePagingSource den aktuellen Schlüssel und gibt den nächsten zu ladenden Schlüssel zurück.
Abbildung 1 Diagramm, das zeigt, wie load den Schlüssel verwendet und aktualisiert.

Die PagingSource Implementierung muss auch eine getRefreshKey Methode implementieren, die ein PagingState Objekt als Parameter akzeptiert. Sie gibt den Schlüssel zurück, der an die Methode load übergeben werden soll, wenn die Daten nach dem ersten Laden aktualisiert oder ungültig gemacht werden. Die Paging-Bibliothek ruft diese Methode bei nachfolgenden Aktualisierungen der Daten automatisch auf.

Fehler verarbeiten

Anfragen zum Laden von Daten können aus verschiedenen Gründen fehlschlagen, insbesondere beim Laden über ein Netzwerk. Melden Sie Fehler, die beim Laden auftreten, indem Sie ein LoadResult.Error-Objekt von der Methode load zurückgeben.

Sie können beispielsweise Ladefehler in ExamplePagingSource aus dem vorherigen Beispiel abfangen und melden, indem Sie der Methode load Folgendes hinzufügen:

catch (e: IOException) {
  // IOException for network failures.
  return LoadResult.Error(e)
} catch (e: HttpException) {
  // HttpException for any non-2xx HTTP status codes.
  return LoadResult.Error(e)
}

Weitere Informationen zum Verarbeiten von Retrofit-Fehlern finden Sie in den Beispielen in der API-Referenz zu PagingSource.

PagingSource erfasst LoadResult.Error-Objekte und stellt sie der UI zur Verfügung, damit Sie darauf reagieren können. Weitere Informationen zum Bereitstellen des Ladestatus in der UI finden Sie unter Ladestatus verwalten und präsentieren.

Stream von PagingData einrichten

Als Nächstes benötigen Sie einen Stream mit Seiten mit Daten aus der PagingSource-Implementierung. Richten Sie den Datenstream in Ihrem ViewModel ein. Die Pager Klasse bietet Methoden, die einen reaktiven Stream von PagingData Objekten aus einer PagingSource bereitstellen. Die Paging-Bibliothek stellt den Datenstream als Flow bereit.

Wenn Sie eine Pager Instanz erstellen, um Ihren reaktiven Stream einzurichten, müssen Sie der Instanz ein PagingConfig Konfigurationsobjekt und eine Funktion bereitstellen, die Pager mitteilt, wie eine Instanz Ihrer PagingSource Implementierung abgerufen wird. Das folgende Beispiel zeigt, wie das geht.

class UserViewModel(
    private val backend: ExampleBackendService,
    private val query: String
) : ViewModel() {

    val userPagingFlow: Flow<PagingData<User>> = Pager(
        // Configure how data is loaded by passing additional properties to
        // PagingConfig, such as pageSize and enabling or disabling placeholders.
        config = PagingConfig(
            pageSize = 20,
            enablePlaceholders = true
        ),
        pagingSourceFactory = {
            ExamplePagingSource(backend, query)
        }
    )
    .flow
    .cachedIn(viewModelScope)
}

Der Operator cachedIn macht den Datenstream freigabefähig und speichert die geladenen Daten mit dem bereitgestellten CoroutineScope im Cache. Ohne cachedIn kann PagingData nicht neu erfasst werden. In diesem Beispiel wird viewModelScope verwendet, das vom Lifecycle-Artefakt lifecycle-viewmodel-ktx bereitgestellt wird.

Das Pager Objekt ruft die load Methode aus dem PagingSource Objekt auf, stellt ihm das LoadParams Objekt zur Verfügung und empfängt im Gegenzug das LoadResult Objekt.

Daten in der UI erfassen und anzeigen

Um den Stream mit Seiten mit Daten mit der UI zu verbinden, rufen Sie den Flow aus Ihrem ViewModel ab und übergeben Sie ihn an Ihre List-Composable.

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val userFlow = viewModel.userPagingFlow
    UserList(flow = userFlow)
}

Verwenden Sie collectAsLazyPagingItems, um den PagingData-Flow in LazyPagingItems zu konvertieren. Verwenden Sie dann die items-API in einer LazyColumn, um die einzelnen Elemente zu layouten.

Stellen Sie mit itemKey eine eindeutige, stabile ID für jedes Element bereit. Im folgenden Beispiel wird it.id verwendet (mit Verweis auf die Eigenschaft User.id), da sie für die Instanz User bei Datenaktualisierungen stabil bleibt.

@Composable
fun UserList(flow: Flow<PagingData<User>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val user = lazyPagingItems[index]
            if (user != null) {
                UserRow(user)
            } else {
                UserPlaceholder()
            }
        }
    }
}

Die Paging-Bibliothek verwendet null für Platzhalter, während eine Seite geladen wird. Wenn Sie Platzhalter aktiviert haben, müssen Sie also null-Werte im Inhaltsblock verarbeiten.

In der Liste werden jetzt die Seiten mit Daten angezeigt und die Paging-Bibliothek lädt weitere Seiten, während der Nutzer scrollt.

Zusätzliche Ressourcen

Weitere Informationen zur Paging-Bibliothek finden Sie in den folgenden zusätzlichen Ressourcen:

Dokumentation

Inhalte ansehen