Pagingライブラリとは
Pagingライブラリとは、ページング処理を楽に実装するためのライブラリです。執筆時点での最新安定板は、2021年5月5日リリースの3.0.0です。Paging 3とは、Pagingライブラリの3.x.xのライブラリを指します。同様に以前使用されていたPaging 2はPagingライブラリの2.x.xのライブラリを指します。
ページング処理とは、簡単に言うと大きなデータを小さなデータの塊ごとに分割し、小さなデータの塊を取得して随時表示するという処理です。
Pagingライブラリを使用することで、一度にデータを大量に取得せずに小さなデータの塊に分割して取得するので、ユーザーのネットワーク帯域幅やシステムリソースを圧迫しないというメリットがあります。
これらの特徴から、無限スクロールなど実装するには大量のデータが必要だが、一度に読み込むとネットワーク帯域幅やシステムリソースを圧迫してしまう機能を実装する際に利用されてきました。
従来のPaging 2の問題点
Paging 3について触れていく前に、まず以前使⽤されていたPaging 2の問題点は⼤きく3つ挙げられます。Paging 3以前に使用されていたPaging 2には、大きく3つの問題点がありました。
1つ目は、データの取得処理が通常の関数であることです。通常の関数だと、処理が終わるまで起動したスレッドを占有してしまうことになるため、suspend関数と比べてパフォーマンスが低くなってしまう傾向にあります。
2つ目は、非同期処理ライブラリであるCoroutinesや、Coroutinesの一種であるFlowへ対応していないことです。昨今のアプリ開発において、CoroutinesやFlowはよく使われていますが、Paging 2はこれらと親和性がなく導入にコストがかかっていました。
3つ目は、読み込みの状態に関する情報をライブラリが提供していないことです。ライブラリが提供されていないため、ローディングやエラーを自分で実装する必要があり、余分なコストがかかっていました。
Paging 3を使うメリット
Paging 3は、上記で挙げたPaging 2の問題点を解決しています。
Paging 3では、データ取得処理の関数がsuspend関数になっており、同期処理よりもパフォーマンスの向上が見込め、読み込み処理のキャンセルなどが行えます。
さらに、CoroutineやFlowへ対応し、これらの最新の技術と親和性があり快適に使用することができます。
最後にPaging 3では、LoadStateListenerというライブラリが提供するリスナーを使うことにより、読み込みやエラーの情報を取得することができます。これにより、自分で実装するよりも正確な読み込み状態に応じた表示を行えます。
Paging 2と3の対応づけ
Paging 3への移行方法は、実装方法により多少変わるものの、Paging 2と3の対応関係を把握することでスムーズに移行できます。公式のマイグレーション記事がとてもわかりやすくまとめられていますので、詳しくは以下をご覧ください。
まず、Paging 2以前で表示に用いられていた、データを保持する役割を担っているDataSourceは、Paging 3ではPagingSourceに変更になりました。DaoからDataSource.Factoryを返却していた場合は、戻り値をPagingSourceに変更するだけで移行ができ、独自に実装していた場合は、PagingSourceを実装する必要性があります。
次に、APIからデータを取得しDBに書き込む役割をはたしていたBoundaryCallBackは、Paging 3ではRemoteMediatorがその役割を担っています。
主な変更点として、これまでBoundaryCallBackは初回ロードや追加読み込みなどが別の関数として提供されており、各々実装する必要性がありました。
しかし、RemoteMediatorでは1つのloadメソッドの中で、初回読み込みかどうかの判別をして処理を行うのでデータ取得の処理が共通となり、より綺麗に実装できるようになっています。
そして、取得したデータを提供する役割を担うLivePagedListなどは、Paging 3ではPagerに置き換わっており、Paging 2などで使用されていたLivePagedListではLiveDataしか返却できなかったものが、PagerではFlowとLiveDataのいずれかの形式で返却できるようになっています。
取得したデータを受け取り、RecyclerViewなどに情報を提供する役割を担うPagedListAdapterは、Paging 3ではPagingDataAdapterとなっており、データのSubmitを非同期処理で行うことが可能となっております。
そのほかのDBやAdapterなどは、ライブラリのアップデートの際に独自の実装などがないかぎりは変更はありません。
導入方法と今回扱うPagingライブラリのバージョン
今回はPaging 3.0.0を対象に扱っていきます。Gradleを用いた導入方法は以下のとおりです。
dependencies { def paging_version = "3.0.0" implementation "androidxpaging:paging-runtime:$paging_version" }
今回開発するサンプルと注意事項
Paging 3を用いた実装方法はさまざまですが、今回は一例として、アニメをレコメンドするAnnictAPIを活用し、アニメをリスト表示するサンプルアプリを作って解説していきます。サンプルコードを動かす際には、AnnictのAPI利用権利をご確認のうえ、トークンを発行しサンプルコード中に埋め込みご利用ください。
サンプルコードは以下のリポジトリになります。
実装の流れ
今回は、Paging 3で良く求められるであろう2つの要件を実装しながら、説明します。
- APIから取得したデータを表示するだけの場合
- APIから取得したデータをDBに保存してキャッシュをする場合
まずは、全体像をつかむためにデータの流れを下の図を見ながら理解していきましょう。
APIから取得したデータを表示するだけの場合
PagingSourceがAPIからデータを取得し、Pagerクラスに引き渡します。PagerクラスをViewModelでFlowまたはLiveDataでFragmentに提供し、FragmentはViewModelのデータをObserveし、PagingDataAdapterにSubmitします。
APIから取得したデータをDBに保存してキャッシュをする場合
まずは、RemoteMediatorがAPIからデータを取得します。今回はAPIから取得したデータを表示するだけの場合とは違い、キャッシュ用のDBに書き込みます。そして、Pagerはキャッシュ用DBに書き込まれたデータを取得し、ViewModel、Fragment、PagingDataAdapterとデータがわたっていきます。
PagerはPagingSourceのデータが不足するとRemoteMediatorにデータを取得するよう要求します。それを受けてRemoteMediatorはAPIからデータを取得しDBに書き込みます。そしてPagerはDBに書き込まれたデータを取得するといった繰り返しになります。
データの流れを理解することで、今書いている処理がいつ使われるものかなどがイメージしやすくなり理解しやすくなりますので、しっかりと理解して実装に移っていきましょう。
Pagerまでのデータの流れ前回の「要件を確認する」の要件ごとに実装が変わるのですが、共通となる部分を実装しながら理解していきましょう。→まずは、要件別の実装方法の紹介に移る前の、共通部分の実装方法を紹介します。