〈kotlin〉Paging3
Android studioでkotlinを使ってAndroidアプリ作成の勉強中。
アプリのUIに表示するデータを段階的に読み込むことが簡単に実装できるライブラリ
Paging3をCodelabを参考に勉強した。
Android Paging Basics | Android Developers
Android Paging Advanced codelab | Android Developers
サンプルコードはここから一部改変。
ページング データを読み込む、表示する | Android デベロッパー | Android Developers
コンポーネント
PagingSource | データのソースと、そのソースからデータを取得する方法を定義 |
---|---|
PagingConfig | ページング動作を決めるパラメータ定義 (一度に読み込む量や初期読み込みサイズなど) |
Pager | PagingDataのインスタンスを公開する |
PagingData | PagingSource オブジェクトに対してクエリを実行した結果を格納する |
PagingAdapter | ページ分けされたデータを処理してUI更新 |
PagingSource
PagingSourceを作成するには、
- データの読み込みに使う識別子となるページングキー
- 読み込むデータの型
- データの取得元
が必要となる。
今回の例ではString型のクエリとInt型のページ番号をRetrofitに渡し、
ネットワークからUser型のオブジェクトを取得するExampleBackendServiceを作成しているとする。
class ExamplePagingSource( val backend: ExampleBackendService, val query: String ) : PagingSource<Int, User>() { override suspend fun load( params: LoadParams<Int>): LoadResult<Int, User> { try { val nextPageNumber = params.key ?: 1 val response = backend.searchUsers(query, nextPageNumber) return LoadResult.Page( data = response.users, prevKey = if( nextPageNumber > 1) nextPageNumber - 1 else null, nextKey = nextPageNumber + 1 ) } catch (e: Exception) { ... return LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState<Int, User>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } }
load() と getRefreshKey() という 2 つの関数を実装する。
load()
ユーザーがスクロールしたときに表示される追加のデータを非同期で取得する。
引数LoadParamsは読み込むページのキーとアイテム数を保持するオブジェクトで、
最初の読み込みではページキーがnullのためキーを定義しておく。
load()はLoadResultを返し、
読み込みが正常に完了するとLoadResult.Page オブジェクト、
読み込みが成功しなかったらLoadResult.Error オブジェクトが返される。
LoadResult.Pageの引数は
data: 取得したアイテムの List
prevKey: 現在のページより前のアイテムを取得する必要がある場合に使用するキー
nextKey: 現在のページより後のアイテムを取得する必要がある場合に使用するキー
getRefreshKey()
初期ロード後にデータが更新または無効化されたときに load() メソッドに渡すキーを返す。
PagingStateのanchorPositionはデータを正常に取得した最後のインデックスで、
これに一番近いページを取得して、次に読み込み始めるページのキーを返す。
PagingData
Pager クラスを使用してPagingSource からの PagingData オブジェクトを公開する。
通常はViewModel内に実装する。
val flow = Pager( PagingConfig(pageSize = 20) ) { ExamplePagingSource(backend, query) }.flow .cachedIn(viewModelScope)
Pagerインスタンスを作成するには、
PagingConfig 構成オブジェクト、PagingSource実装のインスタンスを取得する方法を指示する関数を渡す。
cachedIn() はデータ ストリームを共有可能にし、指定された CoroutineScope で読み込まれたデータをキャッシュに保存する。
(設定またはナビゲーションの変更があってもページング状態を維持できる)
PagingAdapter
PagingDataAdapter を拡張するクラスを定義する。
class UserAdapter : PagingDataAdapter<User, UserViewHolder>(diffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { ... } override fun onBindViewHolder(holder: UserViewHolder, position: Int) { ... } }
UIに表示
PagingData ストリームを監視し、生成されたそれぞれの値をアダプターの submitData() メソッドに渡す。
val pagingAdapter = UserAdapter() val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) recyclerView.adapter = pagingAdapter lifecycleScope.launch { viewModel.flow.collectLatest { pagingData -> pagingAdapter.submitData(pagingData) } }
ネットワークとローカル データベースから同時にページングするためのRemoteMediatorも
codelab内で登場したけど難しかったのでまたいつか...。
Android Paging Advanced codelab | Android Developers