ページング ライブラリには、大規模なデータセットからページング データを読み込み、表示する強力な機能があります。このガイドでは、Paging ライブラリを使用して、ネットワーク データソースからページングされたデータのストリームを設定し、遅延リストに表示する方法について説明します。
データソースを定義する
最初のステップは、データソースを識別する PagingSource 実装を定義することです。PagingSource API クラスには load メソッドが含まれています。このメソッドをオーバーライドして、対応するデータソースからページング データを取得する方法を示します。
非同期読み込みに Kotlin コルーチンを使用するには、PagingSource クラスを直接使います。
キーと値の型を選択する
PagingSource<Key, Value> には、Key と Value の 2 種類のパラメータがあります。キーは、データの読み込みに使用される識別子を定義します。値はデータ自体の型です。たとえば、Int 型のページ番号を Retrofit に渡してネットワークから User オブジェクトのページを読み込む場合は、Key 型として Int を選択し、Value 型として User を選択します。
PagingSource を定義する
次の例では、ページ番号でアイテムのページを読み込む PagingSource を実装しています。Key の型は Int、Value の型は 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)
}
}
}
一般的な PagingSource 実装では、コンストラクタで提供されたパラメータを load メソッドに渡して、クエリに適切なデータを読み込みます。上記の例では、これらのパラメータは次のとおりです。
backend: データを提供するバックエンド サービスのインスタンス。query:backendで示されるサービスに送信する検索クエリ。
LoadParams オブジェクトには、実行する読み込みオペレーションに関する情報が含まれます。これには、読み込むキーと読み込むアイテムの数が含まれます。load 内の読み込み型を区別するには、LoadParams のどのサブクラス(LoadParams.Refresh、LoadParams.Append、LoadParams.Prepend)が渡されたかを確認します。
LoadResult オブジェクトには、読み込みオペレーションの結果が含まれます。LoadResult は、load 呼び出しが成功したかどうかに応じて、次の 3 つの形式のいずれかを取るシールクラスです。
- 読み込みが成功した場合は、
LoadResult.Pageオブジェクトが返されます。その方向にこれ以上データを読み込めない場合は、nextKeyまたはprevKeyにnullを渡します。 - 読み込みが成功しなかった場合は、
LoadResult.Errorオブジェクトが返されます。 - 基になるデータが変更されたために
PagingSourceが無効になった場合は、LoadResult.Invalidオブジェクトを返します。
次の図は、この例の load 関数が各読み込みのキーを受け取り、後続の読み込みのキーを提供する方法を示しています。
load がキーをどのように使用および更新するかを示す図。PagingSource の実装では、PagingState オブジェクトをパラメータとして受け取る getRefreshKey メソッドも実装する必要があります。初期読み込み後にデータが更新または無効化されたときに load メソッドに渡すキーが返されます。その後データが更新されると、Paging ライブラリは自動的にこのメソッドを呼び出します。キーがアイテムの位置を表すオフセット ベースのページネーションの場合、state.anchorPosition を基準として更新キーを計算できます。
// Only for positional or offset PagingSources where Key is a 0-based item index
// rather than a page number.
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Center the refreshed load around the user's current viewport so the list
// does not jump back to the top on invalidation.
return state.anchorPosition?.let { anchorPosition ->
(anchorPosition - state.config.initialLoadSize / 2).coerceAtLeast(0)
}
}
エラーを処理する
特にネットワーク経由でデータを読み込む場合、読み込みリクエストがさまざまな原因で失敗することがあります。load メソッドから LoadResult.Error オブジェクトを返すことにより、読み込み中に発生したエラーを報告できます。
たとえば、load メソッドに以下を追加することで、前の例の ExamplePagingSource での読み込みエラーをキャッチして報告できます。
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)
}
Retrofit エラーの処理について詳しくは、PagingSource API リファレンスのサンプルをご覧ください。
PagingSource は、LoadResult.Error オブジェクトを収集して UI に配信し、ユーザーがそれらを操作できるようにします。UI で読み込み状態を公開する方法について詳しくは、読み込み状態の管理と表示をご覧ください。
PagingData のストリームを設定する
次に、PagingSource 実装からページング データのストリームが必要になります。ViewModel でデータ ストリームを設定します。Pager クラスには、PagingData オブジェクトの PagingSource からのリアクティブ ストリームを表示するメソッドが用意されています。Paging ライブラリは、データのストリームを Flow として公開します。
Pager インスタンスを作成してリアクティブ ストリームを設定する場合は、PagingConfig 構成オブジェクトと、PagingSource 実装のインスタンスを取得する方法を Pager に指示する関数をインスタンスに提供する必要があります。オプションの initialKey を指定することもできます。たとえば、1 ベースのページ番号の場合は 1、オフセットベースのページングの場合は 0 を指定します。PagingConfig では、pageSize、initialLoadSize、prefetchDistance、enablePlaceholders を構成できます。prefetchDistance プロパティは、読み込まれたコンテンツの両端から何個のアイテムが次のフェッチをトリガーするかを定義します。
class UserViewModel(
private val backend: ExampleBackendService,
private val query: String
) : ViewModel() {
val userPagingFlow: Flow<PagingData<User>> = Pager(
config = PagingConfig(
pageSize = 20,
// Load 3 pages initially so the user can scroll immediately without
// hitting a loading state.
initialLoadSize = 60,
// Fetch the next page far enough ahead of the scroll position to
// avoid visible pauses.
prefetchDistance = 20,
enablePlaceholders = true
),
initialKey = 1,
pagingSourceFactory = {
ExamplePagingSource(backend, query)
}
)
.flow
.cachedIn(viewModelScope)
}
cachedIn 演算子はデータ ストリームを共有可能にするとともに、指定された CoroutineScope で読み込まれたデータをキャッシュに保存します。cachedIn がないと、PagingData を再収集できません。この例では、Lifecycle lifecycle-viewmodel-ktx アーティファクトにより提供される viewModelScope を使用しています。
Pager オブジェクトは、PagingSource オブジェクトから load メソッドを呼び出し、LoadParams オブジェクトを提供し、それに応じて LoadResult オブジェクトを受け取ります。
データを収集して UI に表示する
ページングされたストリームを UI に接続するには、ViewModel からフローを取得して、リスト コンポーザブルに渡します。
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
collectAsLazyPagingItems を使用して、PagingData フローを LazyPagingItems に変換します。次に、LazyColumn 内で items API を使用して、各アイテムをレイアウトします。
itemKey を使用して、各アイテムに一意の安定した識別子を指定してください。次の例では、it.id(User.id プロパティを参照)を使用しています。これは、データ更新全体で User インスタンスに対して安定しているためです。
@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()
}
}
}
}
Paging ライブラリは、ページの読み込み中にプレースホルダに null を使用します。そのため、プレースホルダを有効にしている場合は、コンテンツ ブロックで null 値を処理する必要があります。
これで、リストにページングされたデータが表示され、ユーザーがスクロールすると、Paging ライブラリによって追加のページが読み込まれます。
参考情報
ページング ライブラリについて詳しくは、以下の参考情報をご覧ください。
ドキュメント
コンテンツを閲覧する
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- ネットワークとデータベースからページングする
- Paging 3 に移行する
- ページング ライブラリの概要