📝
Paging 3 の使い方を勉強した時のメモ
Paging 3
概要
Android Jetpack の一部で、アプリ内でスクロール可能なリストやグリッドのデータを効率的に読み込んで表示するためのライブラリ。
大量のデータセットから少量ずつデータを読み込むことでメモリ消費を抑え、応答性の高い UI を維持する。
特徴
- データの非同期読み込み:バックグラウンドスレッドでデータを読み込むことで、UIスレッドをブロックせずにデータを読み込む。
- リストの無限スクロール:ユーザーがリストの末尾に到達すると自動的に次のデータセットを読み込む。
- エラーハンドリングと再試行:ネットワークエラーやデータの読み込みエラーが発生した場合に、エラーを表示し、再試行のオプションを提供する。
実装例
Paging 3 の基本的な構成要素
- PagingSource:データソースからページ単位でデータを取得する方法を定義
- Pager:PagingSource を使用して PagingData ストリームを作成
- PagingData:ページングされたデータのコンテナで、UI に表示するデータセットを表す
- LazyPagingItems:PagingData を Jetpack Compose で表示するために使用されるアダプターの役割を果たす
サンプルコード
Ktor で API を叩き、取得したデータのリストを Jetpack Compose で表示する時のサンプル。
依存関係
アプリレベルの build.gradle に以下を追加。
dependencies {
// 略
// 2つ追加
implementation("androidx.paging:paging-runtime-ktx:3.2.1")
implementation("androidx.paging:paging-compose:3.3.0-alpha05")
}
1.PagingSource の定義
import androidx.paging.PagingSource
import androidx.paging.PagingState
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
class MyPagingSource(
private val httpClient: HttpClient // Ktor のクライアント
) : PagingSource<Int, MyData>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyData> {
return try {
val page = params.key ?: 1
val response = withContext(Dispatchers.IO) {
httpClient.get("https://myapi.com/data?page=$page") // レスポンスを取得する
}
val data = Json.decodeFromString<List<MyData>>(response.bodyAsText()) // Json をデシリアライズ
LoadResult.Page(
data = data, // ページのデータ
prevKey = if (page == 1) null else page - 1, // 前のページ番号
nextKey = if (data.isEmpty()) null else page + 1 // 後ろのページ番号
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, MyData>): Int? {
// ここにリフレッシュ時のキーを決定するロジックを書く
return state.anchorPosition
}
}
loadメソッド
ページングデータのロードを行うための主要なメソッド。
非同期でデータをロードし、その結果を LoadResult オブジェクトとして返す。
必要な要素
- params:LoadParams 型のパラメータで、ページング要求の情報を含んでいる。主に、key(現在のページ位置またはページ番号)と loadSize(ロードしたいデータの量)が含まれる。
- 戻り値:LoadResult.Page または LoadResult.Error。正常にデータをロードできたかどうかによって戻り値が変わる。
コード例override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyData> { try { val currentPage = params.key ?: 1 // 最初のページの場合は、デフォルト値として1を使用 val response = // APIからデータをロードするロジック val responseData = // レスポンスからデータを抽出するロジック return LoadResult.Page( data = responseData, prevKey = if (currentPage == 1) null else currentPage - 1, // 最初のページの場合は前のページなし nextKey = if (responseData.isEmpty()) null else currentPage + 1 // データがない場合は次のページなし ) } catch (exception: Exception) { return LoadResult.Error(exception) } }
getRefreshKeyメソッド
getRefreshKeyメソッドは、Pagingライブラリがリフレッシュ操作(下に引っ張って更新など)を行う際に、どのページからロードを再開するかを決定するために使用する。
必要な要素
-
state:PagingState 型の状態情報で、ページングの現在の状態を含んでいる。
コード例
ユーザーがリストのどこにいるのか(anchorPosition) に基づいて、最も近いページのキーを計算し、そのページの前後のページを判断して適切なリフレッシュ位置を返す例。override fun getRefreshKey(state: PagingState<Int, MyData>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } }
2.Pager のセットアップと ViewModel 内での使用
import androidx.lifecycle.ViewModel
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import io.ktor.client.*
import kotlinx.coroutines.flow.Flow
class MyViewModel(
private val httpClient: HttpClient // Ktor のクライアント
) : ViewModel() {
val myDataFlow: Flow<PagingData<MyData>> = Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = { MyPagingSource(httpClient) }
).flow
}
Pagerクラス
ページングデータを非同期にロードし、PagingData のストリームとして提供する。
主要な要素
- PagingConfig:ページングの設定を指定するオブジェクト。ページサイズ、プレフェッチ距離、メモリ内キャッシュのサイズ、プレースホルダーの使用有無などを制御する。
- PagingSource:データソースからページングデータをロードするためのオブジェクト。ローカルデータベースやネットワークAPIからデータを取得するロジックを実装する。
- Pager.flow:ページングデータの Flow<PagingData<T>> を提供する。
コード例class MyViewModel : ViewModel() { // PagingConfigの設定 private val pagingConfig = PagingConfig( pageSize = 20, // 一度にロードするデータの数 enablePlaceholders = false, // プレースホルダーを使用するかどうか prefetchDistance = 5 // ユーザーがスクロールしているときに先読みするデータの数 ) // Pagerオブジェクトの生成と、PagingDataのFlowを提供 val pagingDataFlow: Flow<PagingData<MyData>> = Pager( config = pagingConfig, pagingSourceFactory = { MyPagingSource() } // MyPagingSourceはPagingSourceを継承したクラス ).flow .cachedIn(viewModelScope) // ViewModelのスコープ内でPagingDataをキャッシュする }
3.Jetpack Compose でのデータ表示
@Composable
fun MyDataList(myDataFlow: Flow<PagingData<MyData>>) {
val lazyPagingItems = myDataFlow.collectAsLazyPagingItems()
LazyColumn {
items(lazyPagingItems.itemCount) { index ->
val item = lazyPagingItems[index]
if (item != null) {
Text(text = item.toString()) // 実際にはカスタムデザインを適用
}
}
}
}
おわり
近いうちに RemoteMediator の使い方も学びたい
Discussion