おわりに
Paging 3の基本的な使い方は以上になります。いかがでしたでしょうか。とっかかりはだいぶ難しそうに感じますが、冒頭で説明したデータの流れを理解すると直感的に記述できると思います。
これまで、Stateなどを実装するために独自のResult型を定義してその中にPagerのデータとStateを格納するといった実装も見られました。
しかし、Paging 3を用いることで、PagerやLiveData、Flowなどを返却するだけで済むため、Architectureを守るためにモジュールごとにResult型などを定義するといったことをする必要が無くなります。
個人的には、このPagerに処理や必要なクラスがまとめられていて、なおかつLiveDataやFlowでデータのみを提供できるといった部分が一番魅力的なポイントだと感じました。
理解が難しかった点としては、やはりRemoteMediatorのLoadTypeだと思います。従来のPaging 2などでは、初期読み込みと追加読み込みの関数がありボイラープレートとはいえ関数の目的がはっきりしているためわかりやすいです。
しかし、RemoteMediatorのloadでは上記のボイラープレートをなくすため、LoadTypeによって読み込むpageを決定します。その際の、REFRESH、PREPEND、APPENDのpageに指定すべきかといった部分がCodeLabなどをやるだけでは理解がなかなか難しかったように思います。そのため、今回はRemoteMediatorを分解して解説を行いました。
個人的には、LoadTypeを一つひとつ理解して、それを踏まえ要件をしっかり認識していたらどのようにpageを指定すべきかわかりやすかったように思います。
Paging 3を使用することで、よりシンプルで拡張性が高く、プロジェクトに新規参入した人も理解しやすいようなコードに書き換えていけると思います! Androidを楽しみましょう!
補足:発展的な内容
ソートやクエリなどのパラメータを変えたときに再度Pagingを呼び出してデータを取り直す方法
再読み込みをするだけなら、ほかのPagerを使用して読み出しを始めた際にPaging 3のほうで既存のデータ取得をキャンセルしてから新しい方のPagerの呼び出しを始めてくれるためとても簡単です。ソートやクエリなどはViewModel側で持ちますので、sessionIdなどを含めてViewModelから渡してあげる必要があります。
そこで、Repositoryの内部でsessionIdを生成していたのですが、ViewModelで生成して渡すようにします。クエリなどを渡す場合は適宜RemoteMediatorやAPIをクエリが渡せるよう実装してください。そして、データを取り直す際にsessionIdを変更してあげることで新しいクエリでデータを取得できるようになります。
さらに、上記で必須ではないsessionIdを作ったことがここで活きてきますが、sessionIdでDBから読み込んでいるため過去のデータが残っていたとしても読み込まれることがありません。それによって、古いデータが表示されることや、ちらつきといった部分も軽減することができます。
今回はよりシンプルにするためにsessionIdのみのQueryにしていますが、ここにAPIに指定するSortなどを追加してください。
data class SampleQuery( val sessionId: String = UUID.ramdomUUID().toString() )
次に、Repositoryの内部で作成していたsessionIdを引数で渡すように変更します。
// 変更前 fun getAnime(): Pager<Int, Anime> { val sessionId = UUID.randomUUID().toString() // 変更後 fun getAnime(query: SampleQuery): Pager<Int, Anime> {
クエリをLiveDataでもち、switchMapを使いRepositoryのgetAnimesを呼ぶことで、Queryに変更があると再度getAnimesがトリガーされ、新たに読み込みが始まるためクエリに変更があると再度データを取得する実装が実現できます。
// クエリ private var query = SampleQuery( sessionId = UUID.ramdomUUID().toString() ) // animeListをFragmentでobserveしてAdapterにsubmitする val animeList: LiveData<PagingData<Anime>> = query.switchMap { changedQuery -> animeRepository.getAnimes(changedQuery).liveData } fun updateQuery() { query.value = SampleQuery(UUID.randomUUID().toString()) /** * 複数Queryに値がある場合はcopyを使用すると、指定個所のみ値を変更できるため便利です * * query.value = query.value?.copy(sessionId = UUID.randomUUID().toString(), hoge = huga) */ }
StateAdapterを使ってデータ読み込み中などの情報を取得する方法
Paging 3のメリットで挙げていましたが、Paging 3ではローディングなどの状態を取得することができます。具体的な方法としては、FragmentでPagingDataAdapterを継承したAdapterにaddLoadStateListenerをセットすることでLoadStateを取得することができます。
今回は、初期読み込みでロード中ならプログレスバーを表示、追加読み込みでエラーの場合はエラービューを表示します。
具体的な実装は以下になります。
adapter.addLoadStateListener { loadState -> binding.progressBar.visibility = if (loadState.refresh is LoadState.Loading) VISIBLE else GONE val errorState = loadState.source.append is LoadState.Error || loadState.mediator?.append is LoadState.Error binding.errorView.visibility = if (errorState) VISIBLE else GONE