Skip to content

AndroidX Paging

SQLDelightをAndroidのPaging 3ライブラリで使用するには、paging extensionアーティファクトへの依存関係を追加します。

kotlin
dependencies {
  implementation("app.cash.sqldelight:androidx-paging3-extensions:2.1.0")
}
groovy
dependencies {
  implementation "app.cash.sqldelight:androidx-paging3-extensions:2.1.0"
}

SQLDelightは、データのページングのために、オフセットベースのページングとキーセットページングの2つのメソッドを提供しています。

オフセットベースのページング (Offset Based Paging)

オフセットページングは、OFFSET句とLIMIT句を使用してページングの結果を取得します。オフセットベースのページングを実行するPagingSourceを作成するには、件数取得(count)クエリとページングクエリの両方が必要です。

sql
countPlayers:
SELECT count(*) FROM hockeyPlayer;

players:
SELECT *
FROM hockeyPlayer
LIMIT :limit OFFSET :offset;
kotlin
import app.cash.sqldelight.android.paging3.QueryPagingSource

val pagingSource: PagingSource = QueryPagingSource(
  countQuery = playerQueries.countPlayers(),
  transacter = playerQueries,
  context = Dispatchers.IO,
  queryProvider = playerQueries::players,
)

コンテキストが指定されていない場合、デフォルトでクエリはDispatchers.IO上で実行されます。クエリの実行にRxJavaのSchedulerを使用したい場合は、Scheduler.asCoroutineDispatcher拡張関数を使用してください。

キーセットページング (Keyset Paging)

オフセットページングはシンプルでメンテナンスが容易です。しかし、残念ながら大規模なデータセットではパフォーマンスが低下します。SQLステートメントのOFFSET句は、実際にはSQLクエリですでに実行された行を単に破棄するだけです。そのため、OFFSETの数が増えるにつれて、クエリの実行にかかる時間も増加します。これを克服するために、SQLDelightはPagingSourceの「キーセットページング (keyset paging)」の実装を提供しています。データセット全体をクエリして最初のOFFSET要素を非効率に破棄するのではなく、キーセットページングはユニークな列を使用してクエリの範囲を制限します。これによりパフォーマンスは向上しますが、開発者のメンテナンス負荷は高くなります。

このページングソースが受け取るqueryProviderコールバックには、2つのパラメータがあります。null不可でユニークなKeyであるbeginInclusiveと、null許容でユニークなKey?であるendExclusiveです。コアとなるページングクエリの例を以下に示します。

sql
keyedQuery:
SELECT * FROM hockeyPlayer
WHERE id >= :beginInclusive AND (id < :endExclusive OR :endExclusive IS NULL)
ORDER BY id ASC;

キーセットページングで使用されるクエリは、上記のようにユニークな順序(ordering)を持っている必要があります。

beginInclusiveendExclusiveはどちらも、ページの境界として機能する 事前計算(pre-calculated) されたキーです。ページサイズは、ページの境界を事前計算するときに確立されます。pageBoundariesProviderコールバックは、anchor: Key?パラメータとlimit: Int?パラメータを受け取ります。ページの境界を事前計算するクエリの例を以下に示します。

sql
pageBoundaries:
SELECT id 
FROM (
  SELECT
    id,
    CASE
      WHEN ((row_number() OVER(ORDER BY id ASC) - 0) % :limit) = 0 THEN 1
      WHEN id = :anchor THEN 1
      ELSE 0
    END page_boundary;
  FROM hockeyPlayer
  ORDER BY id ASC
)
WHERE page_boundary = 1;

SQLクエリのページ境界の事前計算には、多くの場合SQLite Window Functionsが必要になります。ウィンドウ関数はSQLiteバージョン3.25.0で導入されたため、Android API 30まではデフォルトでは利用できません。キーセットページングを使用するために、SQLDelightはminApi 30を設定するか、独自のSQLiteバージョンを同梱(bundle)することを推奨しています。Requery組織は、スタンドアロンライブラリとしてSQLiteの最新のディストリビューションを提供しています。

AndroidXのpagingライブラリでは、PagingConfig.initialLoadSizeを使用して、最初のページのフェッチサイズを後続のページのフェッチサイズと異なるものに設定できます。この機能は避けるべきです。なぜなら、pageBoundariesProviderコールバックは最初のページフェッチ時に一度だけ呼び出されるからです。PagingConifg.initialLoadSizePagingConfig.pageSizeが一致しない場合、予期しないページ境界の生成結果を招くことになります。

このページングソースは、ジャンプ(jumping)を サポートしていません

このページングソースを作成するには、QueryPagingSourceファクトリ関数を使用します。

kotlin
import app.cash.sqldelight.android.paging3.QueryPagingSource

val keyedSource = QueryPagingSource(
  transacter = playerQueries,
  context = Dispatchers.IO,
  pageBoundariesProvider = playerQueries::pageBoundaries,
  queryProvider = playerQueries::keyedQuery,
)

コンテキストが指定されていない場合、デフォルトでクエリはDispatchers.IO上で実行されます。クエリの実行にRxJavaのSchedulerを使用したい場合は、Scheduler.asCoroutineDispatcher拡張関数を使用してください。