PagingSource i Pager
Zdefiniuj sposób wczytywania stron
PagingSource i Pager to bezpłatna lekcja Android Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Android Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Android Academy zawiera 4 lekcji w sumie.
Definiowanie sposobu ładowania stron
Aby stronicować dane z sieciowego API, należy napisać element PagingSource. Odpowiada on na dwa pytania dotyczące Paging 3:
- Jak załadować stronę dla danego klucza?
- Od którego miejsca wznowić działanie po odświeżeniu przez użytkownika?
Następnie należy opakować go w element Pager, który przekształca go w obiekt Flow<PagingData>.
Parametry typów PagingSource
PagingSource<Key, Value> przyjmuje dwa parametry typu:
- Key - identyfikuje stronę. W przypadku API opartego na numerach stron jest to
Int, a w przypadku API opartego na kursorach jest to token typuString. - Value - typ elementu, na przykład
Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
// implement load() and getRefreshKey()
}Implementowanie load()
load() jest funkcją suspend. Otrzymuje params.key (stronę do pobrania) i zwraca obiekt LoadResult.
W przypadku powodzenia należy zwrócić LoadResult.Page zawierający elementy oraz poprzedni i następny klucz. Wartość null klucza oznacza, że w danym kierunku nie ma już strony.
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Article> {
val page = params.key ?: 1 // first load has a null key
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}Dlaczego prevKey i nextKey mają znaczenie
Paging używa nextKey, aby ładować kolejne strony podczas przewijania w dół, oraz prevKey, aby ładować poprzednie strony podczas przewijania w górę (co jest przydatne przy rozpoczynaniu w środku listy).
Zwrócenie null jako nextKey informuje Paging, że nie ma więcej stron, więc przestaje wysyłać żądania. Pominięcie tego może powodować nieskończone żądania zwracające puste wyniki.
// Stop forward paging when the server returns an empty page
nextKey = if (response.items.isEmpty()) null else page + 1
// Stop backward paging at the first page
prevKey = if (page == 1) null else page - 1Implementowanie getRefreshKey()
Podczas odświeżania listy (gestu przeciągnięcia w celu odświeżenia lub unieważnienia) Paging musi wiedzieć, którą stronę załadować ponownie, aby użytkownik pozostał mniej więcej w tym samym miejscu.
getRefreshKey() używa bieżącej wartości anchorPosition - elementu znajdującego się najbliżej obszaru widoku - aby wybrać odpowiedni klucz.
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchor ->
val closestPage = state.closestPageToPosition(anchor)
closestPage?.prevKey?.plus(1)
?: closestPage?.nextKey?.minus(1)
}
}Eleganckie obsługiwanie błędów
Wywołanie sieciowe należy umieścić w bloku try/catch, a w przypadku błędu zwrócić LoadResult.Error(e). Paging udostępni ten błąd w interfejsie użytkownika jako LoadState.Error, dzięki czemu można wyświetlić przycisk ponowienia.
Nie wolno dopuścić, aby wyjątek wydostał się z load() - należy go przechwycić i przekształcić w LoadResult.Error.
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: IOException) { // no network
LoadResult.Error(e)
} catch (e: HttpException) { // non-2xx response
LoadResult.Error(e)
}Tworzenie elementu Pager
Pager łączy element PagingConfig z fabryką tworzącą nowy element PagingSource. Jego właściwość .flow to obiekt Flow<PagingData>, który zbiera interfejs użytkownika.
Lambda fabryki musi za każdym razem tworzyć nowe źródło, ponieważ Paging unieważnia i odtwarza źródło podczas odświeżania.
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articleStream(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20, prefetchDistance = 5),
pagingSourceFactory = { ArticlePagingSource(api) }
).flow
}cachedIn w ViewModelach
Zbieranie obiektu PagingData jest operacją jednorazową; ponowne zebranie uruchamia ładowanie od początku. Aby zachować dane po zmianach konfiguracji i umożliwić współdzielenie danych przez wielu odbiorców, należy buforować przepływ w viewModelScope za pomocą cachedIn.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
class ArticleViewModel(
repo: ArticleRepository
) : ViewModel() {
val articles = repo.articleStream()
.cachedIn(viewModelScope)
}loadSize a pageSize
Należy pamiętać, że load() odczytuje params.loadSize, a nie bezpośrednio skonfigurowaną wartość pageSize.
Przy pierwszym ładowaniu Paging może zażądać większego początkowego fragmentu (kontrolowanego przez initialLoadSize w PagingConfig, domyślnie równego trzykrotności rozmiaru strony). Należy zawsze przekazywać do API wartość params.loadSize, aby żądanie odpowiadało oczekiwanej przez Paging liczbie elementów.
// Correct: respect the size Paging asks for
val response = api.getArticles(page = page, size = params.loadSize)
// PagingConfig can tune the first load:
PagingConfig(pageSize = 20, initialLoadSize = 40)API oparte na kursorach
Nie każde API używa numerów stron. Niektóre zwracają kursor lub token wskazujący następną stronę. Schemat jest identyczny - wystarczy zmienić typ Key na String i użyć tokenu z odpowiedzi.
class CursorArticleSource(
private val api: ArticleApi
) : PagingSource<String, Article>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Article> {
val cursor = params.key // null on first load
return try {
val res = api.getArticles(cursor = cursor, size = params.loadSize)
LoadResult.Page(
data = res.items,
prevKey = null, // forward-only cursor
nextKey = res.nextCursor // null when exhausted
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<String, Article>) = null
}Łączenie wszystkich elementów
Mają już Państwo kompletną warstwę danych: element PagingSource ładowany stronami, element Pager strumieniujący strony oraz ViewModel buforujący przepływ.
Warstwa interfejsu użytkownika po prostu zbiera viewModel.articles - wyrenderujemy je w następnej lekcji za pomocą Compose LazyColumn.
// Data layer summary
// 1. ArticlePagingSource : PagingSource<Int, Article>
// 2. Pager(config, factory).flow -> Flow<PagingData<Article>>
// 3. ViewModel: repo.articleStream().cachedIn(viewModelScope)
// UI just collects viewModel.articlesSzybkie sprawdzenie
Co sygnalizuje zwrócenie nextKey = null z funkcji load() w obiekcie PagingSource opartym na numerach stron?
Podsumowanie: PagingSource i Pager
Utworzyli Państwo warstwę danych do stronicowania:
PagingSource<Key, Value>implementuje funkcjeload()igetRefreshKey()load()zwracaLoadResult.Pagez wartościamiprevKey/nextKeyalboLoadResult.Error- Klucz o wartości
nullzatrzymuje stronicowanie w danym kierunku Pager(config, factory).flowtworzy obiektFlow<PagingData>cachedIn(viewModelScope)zachowuje dane po zmianach konfiguracji
Następnie: renderowanie tego przepływu na liście Compose.
Ucz się Kotlin dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 36
- Lekcje
- 152
Często zadawane pytania
Czy lekcja „PagingSource i Pager” jest bezpłatna?
Tak — pełny tekst „PagingSource i Pager” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Android Academy, przejdź na CoddyKit PRO. Kurs Android Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „PagingSource i Pager”?
Zdefiniuj sposób wczytywania stron Ćwiczysz Android Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Android Academy?
Nie wymagamy żadnego doświadczenia. Android Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „PagingSource i Pager”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Android Academy?
Tak. Każda lekcja Android Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Dlaczego warto stosować Paging
- PagingSource i Pager
- Paging na listach Compose
- RemoteMediator i buforowanie