PagingSourceとPager
ページの読み込み方法を定義します。
「PagingSourceとPager」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。
ページの読み込み方法を定義する
ネットワークAPIをページングするには、PagingSourceを作成します。Paging 3に対して、次の2つの問いに答える役割を担います。
- 指定されたキーのページをどのように読み込むか
- ユーザーが更新したとき、どこから再開するか
次に、それをPagerでラップして、Flow<PagingData>に変換します。
PagingSource の型パラメーター
PagingSource<Key, Value>は2つの型パラメーターを受け取ります。
- Key - ページを識別します。ページ番号を使うAPIでは
Int、カーソルAPIではStringトークンです。 - Value - 項目の型です。たとえば
Articleです。
import androidx.paging.PagingSource
import androidx.paging.PagingState
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
// implement load() and getRefreshKey()
}load() の実装
load()はsuspend関数です。params.key(取得するページ)を受け取り、LoadResultを返します。
成功した場合は、項目と前後のキーを含むLoadResult.Pageを返します。キーがnullの場合、その方向にはページがないことを表します。
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)
}
}prevKey と nextKey が重要な理由
Pagingは、ユーザーが下へスクロールするときにnextKeyを使って次のページを読み込み、上へスクロールするときにprevKeyを使って前のページを読み込みます(リストの途中から開始する場合に便利です)。
nextKeyにnullを返すと、Pagingはこれ以上ページがないと判断し、リクエストを停止します。これを忘れると、空のリクエストが無限に発生する可能性があります。
// 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 - 1getRefreshKey() の実装
リストが更新されたとき(プル・トゥ・リフレッシュや無効化など)、ユーザーがほぼ同じ位置に留まれるように、Pagingは再読み込みするページを知る必要があります。
getRefreshKey()は現在のanchorPosition(ビューポートに最も近い項目の位置)を使って、適切なキーを選びます。
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)
}
}エラーを適切に処理する
ネットワーク呼び出しをtry/catchで囲み、失敗時にはLoadResult.Error(e)を返します。PagingはこれをUIのLoadState.Errorとして公開するため、再試行ボタンを表示できます。
例外をload()の外へそのまま伝播させないでください。捕捉して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)
}Pager の作成
Pagerは、PagingConfigと新しいPagingSourceを構築するファクトリを結び付けます。その.flowプロパティは、UIが収集するFlow<PagingData>です。
Pagingは更新時にソースを無効化して再作成するため、ファクトリラムダは呼び出されるたびに新しいソースを作成する必要があります。
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
}ViewModel での cachedIn
PagingDataの収集は1回限りの操作です。再度収集すると読み込みが再開されます。構成変更をまたいで状態を維持し、複数のコレクターでデータを共有するには、cachedInを使ってviewModelScope内にFlowをキャッシュします。
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
class ArticleViewModel(
repo: ArticleRepository
) : ViewModel() {
val articles = repo.articleStream()
.cachedIn(viewModelScope)
}loadSize と pageSize
load()が読み取るのは、設定したpageSizeではなくparams.loadSizeであることに注意してください。
最初の読み込みでは、Pagingがより大きな初期チャンクをリクエストする場合があります(PagingConfigのinitialLoadSizeで制御され、デフォルトではページサイズの3倍です)。Pagingが返すことを想定しているサイズとリクエストを一致させるため、必ずparams.loadSizeをAPIに渡してください。
// 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
すべてのAPIがページ番号を使うわけではありません。次のページを指すカーソルやトークンを返すAPIもあります。パターンは同じで、Keyの型をStringに変更し、レスポンスから取得したトークンを使うだけです。
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
}全体を組み合わせる
これでデータレイヤー全体が完成しました。1ページを読み込むPagingSource、ページをストリーミングするPager、そしてFlowをキャッシュするViewModelです。
UIレイヤーではviewModel.articlesを収集するだけです。次のレッスンで、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.articles確認問題
ページ番号を使うPagingSourceで、load()からnextKey = nullを返すと何を示しますか?
まとめ:PagingSource と Pager
ページングのデータレイヤーを構築しました。
PagingSource<Key, Value>はload()とgetRefreshKey()を実装するload()はprevKey/nextKeyを含むLoadResult.Page、またはLoadResult.Errorを返す- キーが
nullになると、その方向のページングが停止する Pager(config, factory).flowはFlow<PagingData>を生成するcachedIn(viewModelScope)により、構成変更後もデータを維持できる
次は、このFlowをComposeのリストに表示します。
よくある質問
「PagingSourceとPager」レッスンは無料ですか?
はい。「PagingSourceとPager」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。
「PagingSourceとPager」で何を学びますか?
ページの読み込み方法を定義します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Android Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「PagingSourceとPager」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAndroid Academyレッスンでコードを書いて実行できますか?
はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Pagingが必要な理由
- PagingSourceとPager
- ComposeのリストでPagingを使う
- RemoteMediatorとキャッシュ