Android Academy · Oppitunti

PagingSource ja Pager

Määritelkää, miten sivut ladataan.

Oppitunti 2/413 vaihetta

PagingSource ja Pager on ilmainen Android Academy-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Android Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Android Academy-kurssilla on yhteensä 4 oppituntia.

Sivujen lataustavan määrittäminen

Kun sivutatte dataa verkon API-rajapinnasta, kirjoitatte PagingSource-komponentin. Se vastaa kahteen Paging 3:n kysymykseen:

  • Miten lataan tietyn avaimen mukaisen sivun?
  • Mistä kohdasta jatkan, jos käyttäjä päivittää näkymän?

Sen jälkeen kääritte sen Pager-komponenttiin, joka muuntaa sen Flow<PagingData>-virraksi.

PagingSource-tyyppiparametrit

PagingSource<Key, Value> ottaa kaksi tyyppiparametria:

  • Key – tunnistaa sivun. Sivunumeropohjaisessa API:ssa se on Int; kursoripohjaisessa API:ssa se on String-tunniste.
  • Value – kohteiden tyyppi, esimerkiksi Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState

class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {
    // implement load() and getRefreshKey()
}

load()-funktion toteuttaminen

load() on suspend-funktio. Se vastaanottaa arvon params.key (haettavan sivun) ja palauttaa LoadResult-arvon.

Onnistuneessa tapauksessa palautatte LoadResult.Page-arvon, joka sisältää kohteet sekä edellisen ja seuraavan avaimen. Avaimen arvo null tarkoittaa, ettei kyseisessä suunnassa ole sivua.

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)
    }
}

Miksi prevKey ja nextKey ovat tärkeitä

Paging käyttää arvoa nextKey ladatakseen sivuja eteenpäin käyttäjän vierittäessä alaspäin ja arvoa prevKey ladatakseen sivuja taaksepäin (mikä on hyödyllistä, kun aloitetaan luettelon keskeltä).

Arvon null palauttaminen kentälle nextKey kertoo Pagingille, ettei sivuja ole enää jäljellä, joten se lopettaa pyynnöt. Jos tämä unohtuu, seurauksena voi olla loputtomia tyhjiä pyyntöjä.

// 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 - 1

getRefreshKey()-funktion toteuttaminen

Kun luettelo päivitetään (vetämällä päivitettäessä tai mitätöinnin jälkeen), Pagingin on tiedettävä, mikä sivu ladataan uudelleen, jotta käyttäjä pysyy suunnilleen samassa kohdassa.

getRefreshKey() käyttää nykyistä anchorPosition-arvoa – näkymäporttia lähinnä olevaa kohdetta – valitakseen sopivan avaimen.

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)
    }
}

Virheiden hallinta siististi

Käärikää verkkokutsu try/catch-rakenteeseen ja palauttakaa virhetilanteessa LoadResult.Error(e). Paging välittää tämän käyttöliittymään LoadState.Error-tilana, joten voitte näyttää uudelleenyrityspainikkeen.

Älkää koskaan päästäkö poikkeusta karkaamaan load()-funktiosta – siepatkaa se ja muuntakaa se LoadResult.Error-arvoksi.

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)
}

Pagerin luominen

Pager yhdistää PagingConfig-asetukset tehtaaseen, joka luo uuden PagingSource-komponentin. Sen .flow-ominaisuus on Flow<PagingData>, jonka käyttöliittymä kerää.

Tehdaslambda-funktion on luotava joka kerta uusi lähde, koska Paging mitätöi lähteen ja luo sen uudelleen päivityksen yhteydessä.

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 ViewModel-komponenteille

PagingData-arvon kerääminen on kertaluonteinen toiminto; uusi keräys käynnistää lataamisen uudelleen. Jotta data säilyy konfiguraatiomuutosten yli ja useat kerääjät voivat jakaa sen, tallentakaa virta välimuistiin viewModelScope-laajuudessa käyttämällä cachedIn-funktiota.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn

class ArticleViewModel(
    repo: ArticleRepository
) : ViewModel() {
    val articles = repo.articleStream()
        .cachedIn(viewModelScope)
}

loadSize ja pageSize

Huomioikaa, että load() lukee arvon params.loadSize, ei suoraan määrittämäänne pageSize-arvoa.

Ensimmäisellä latauskerralla Paging saattaa pyytää suuremman alkujoukon. Sen kokoa hallitsee PagingConfig-asetuksen initialLoadSize, jonka oletusarvo on kolminkertainen sivukoko. Välittäkää API-rajapinnalle aina params.loadSize, jotta pyyntö vastaa Pagingin odottamaa vastausta.

// 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)

Kursoripohjaiset API-rajapinnat

Kaikki API-rajapinnat eivät käytä sivunumeroita. Jotkin palauttavat kursorin tai tunnisteen, joka osoittaa seuraavaan sivuun. Menetelmä on sama – vaihtakaa vain Key-tyypiksi String ja käyttäkää vastauksesta saatua tunnistetta.

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
}

Kokonaisuuden yhdistäminen

Teillä on nyt koko datakerros: yhden sivun lataava PagingSource, sivuja virtauttava Pager sekä virran välimuistiin tallentava ViewModel.

Käyttöliittymäkerros kerää yksinkertaisesti arvon viewModel.articles – seuraavalla oppitunnilla näytämme sen Compose-ympäristössä LazyColumn-komponentilla.

// 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.articles

Pikatarkistus

Mitä sivunumeropohjaisessa PagingSource-komponentissa ilmaistaan palauttamalla nextKey = null load()-funktiosta?

Kertaus: PagingSource ja Pager

Rakensitte sivutuksen datakerroksen:

  • PagingSource<Key, Value> toteuttaa funktiot load() ja getRefreshKey()
  • load() palauttaa LoadResult.Page-arvon kentillä prevKey/nextKey tai arvon LoadResult.Error
  • null-avain lopettaa sivutuksen kyseisessä suunnassa
  • Pager(config, factory).flow tuottaa arvon Flow<PagingData>
  • cachedIn(viewModelScope) säilyttää datan konfiguraatiomuutosten yli

Seuraavaksi näytätte tämän virran Compose-luettelossa.

Aloita maksutta

Opi Kotlin tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
36
Oppitunnit
152

Usein kysytyt kysymykset

Onko oppitunti ”PagingSource ja Pager” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Android Academy-oppimispolun 3 oppituntia, myös oppitunnin “PagingSource ja Pager”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Android Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”PagingSource ja Pager”?

Määritelkää, miten sivut ladataan. Harjoittelet Android Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Android Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Android Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.

Kuinka kauan ”PagingSource ja Pager”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Android Academy-oppitunnilla?

Kyllä. Jokainen Android Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Miksi sivutus on tarpeen
  2. PagingSource ja Pager
  3. Sivutus Compose-luetteloissa
  4. RemoteMediator ja välimuistitus
← Takaisin: Android Academy