Biblioteka Paging zapewnia zaawansowane funkcje wczytywania i wyświetlania danych podzielonych na strony z większego zbioru danych. Z tego przewodnika dowiesz się, jak używać biblioteki Paging do konfigurowania strumienia danych podzielonych na strony ze źródła danych w sieci i wyświetlania ich na liście leniwej.
Określanie źródła danych
Pierwszym krokiem jest zdefiniowanie implementacji PagingSource, aby zidentyfikować źródło danych. Klasa interfejsu API PagingSource zawiera metodę load, którą możesz zastąpić, aby określić sposób pobierania danych podzielonych na strony z odpowiedniego źródła danych.
Użyj bezpośrednio klasy PagingSource, aby używać współprogramów Kotlin do asynchronicznego wczytywania.
Wybieranie typów kluczy i wartości
PagingSource<Key, Value> ma 2 parametry typu: Key i Value. Klucz określa identyfikator używany do wczytywania danych, a wartość to typ samych danych. Jeśli np. wczytujesz strony User obiektów z sieci, przekazując Int numery stron do Retrofit, wybierz Int jako typ Key i User jako typ Value.
Zdefiniuj PagingSource
W przykładzie poniżej zaimplementowano PagingSource, który wczytuje strony z elementami według numeru strony. Typ Key to Int, a typ Value to 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)
}
}
}
Typowa implementacja PagingSource przekazuje parametry podane w konstruktorze do metody load, aby wczytać odpowiednie dane dla zapytania. W przykładzie powyżej są to te parametry:
backend: instancja usługi backendu, która udostępnia dane.query: zapytanie wyszukiwania, które ma zostać wysłane do usługi wskazanej przezbackend.
Obiekt LoadParams zawiera informacje o operacji wczytywania, która ma zostać wykonana. Obejmuje to klucz do wczytania i liczbę elementów do wczytania.
Obiekt LoadResult zawiera wynik operacji wczytywania. LoadResult to klasa zamknięta, która przyjmuje jedną z 3 form w zależności od tego, czy wywołanie load się powiodło:
- Jeśli wczytywanie się powiedzie, zwróć obiekt
LoadResult.Page. - Jeśli wczytywanie się nie powiedzie, zwróć obiekt
LoadResult.Error. - Jeśli
PagingSourcenie jest już ważny i należy go zastąpić nową instancją (np. z powodu zmiany danych źródłowych), zwróć obiektPagingSource.LoadResult.Invalid
Ilustracja poniżej pokazuje, jak funkcja load w tym przykładzie odbiera klucz dla każdego wczytania i udostępnia klucz dla kolejnego wczytania.
load używa klucza i go aktualizuje.
Implementacja PagingSource musi też implementować metodę getRefreshKey, która przyjmuje obiekt PagingState jako parametr. Zwraca klucz, który należy przekazać do metody load, gdy dane są odświeżane lub unieważniane po początkowym wczytaniu. Biblioteka Paging automatycznie wywołuje tę metodę podczas kolejnych odświeżeń danych.
Obsługuj błędy
Żądania wczytania danych mogą się nie powieść z różnych powodów, zwłaszcza podczas wczytywania przez sieć. Zgłaszaj błędy napotkane podczas wczytywania, zwracając obiekt LoadResult.Error z metody load.
Na przykład błędy wczytywania w ExamplePagingSource z poprzedniego przykładu możesz przechwycić i zgłosić, dodając do metody load ten kod:
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)
}
Więcej informacji o obsłudze błędów Retrofit znajdziesz w przykładach w PagingSourcedokumentacji interfejsu API.
PagingSource zbiera i przesyła LoadResult.Error obiekty do interfejsu, aby umożliwić Ci podjęcie działań. Więcej informacji o wyświetlaniu stanu wczytywania w interfejsie znajdziesz w artykule Zarządzanie stanami wczytywania i ich prezentowanie.
Konfigurowanie strumienia PagingData
Następnie potrzebujesz strumienia danych podzielonych na strony z implementacji PagingSource.
Skonfiguruj strumień danych w usłudze ViewModel. Klasa Pager udostępnia metody, które udostępniają reaktywny strumień obiektów PagingData z PagingSource. Biblioteka Paging udostępnia strumień danych jako Flow.
Podczas tworzenia instancji Pager w celu skonfigurowania strumienia reaktywnego musisz podać instancji obiekt konfiguracji PagingConfig i funkcję, która informuje Pager, jak uzyskać instancję implementacji PagingSource, jak pokazano w tym przykładzie.
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)
}
Operator cachedIn udostępnia strumień danych i buforuje załadowane dane za pomocą podanego CoroutineScope. Bez cachedIn nie można ponownie zebrać PagingData. W tym przykładzie używamy viewModelScope dostarczonego przez artefakt lifecycle-viewmodel-ktx cyklu życia.
Obiekt Pager wywołuje metodę load z obiektu PagingSource, przekazując mu obiekt LoadParams i otrzymując w zamian obiekt LoadResult.
Zbieranie i wyświetlanie danych w interfejsie
Aby połączyć strumień podzielony na strony z interfejsem, pobierz przepływ z ViewModel i przekaż go do funkcji kompozycyjnej listy.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
Użyj collectAsLazyPagingItems, aby przekształcić PagingData w LazyPagingItems. Następnie użyj interfejsu items API w ramach elementu LazyColumn, aby rozmieścić poszczególne elementy.
Pamiętaj, aby podać unikalny, stały identyfikator każdego produktu za pomocą atrybutu itemKey.
W poniższym przykładzie użyto it.id (odwołującego się do właściwości User.id), ponieważ pozostaje on stabilny w przypadku instancji User w trakcie aktualizacji danych.
@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()
}
}
}
}
Biblioteka Paging używa wartości null jako obiektów zastępczych podczas wczytywania strony, więc jeśli masz włączone obiekty zastępcze, musisz obsługiwać wartości null w bloku treści.
Lista wyświetla teraz dane podzielone na strony, a biblioteka Paging wczytuje dodatkowe strony podczas przewijania przez użytkownika.
Dodatkowe materiały
Więcej informacji o bibliotece Paging znajdziesz w tych materiałach:
Dokumentacja
Wyświetlanie treści
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Strona z sieci i bazy danych
- Migracja do biblioteki Paging 3
- Omówienie biblioteki Paging