Skip to content

AndroidX ページング

SQLDelight を AndroidのPaging 3ライブラリ とともに使用するには、ページング拡張アーティファクトへの依存関係を追加します。

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句とLIMIT句を使用してページングされた結果を実現します。オフセットベースのページングを実行するPagingSourceを作成するには、カウントクエリとページングされるクエリの両方が必要です。

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拡張関数を使用すべきです。

キーセットページング

オフセットページングはシンプルで保守が容易です。残念ながら、大規模なデータセットではパフォーマンスが低下します。SQLステートメントのOFFSET句は、実際にはSQLクエリですでに実行された行を単に破棄するだけです。したがって、OFFSETの数値が増加するにつれて、クエリの実行にかかる時間も増加します。これを克服するために、SQLDelightはPagingSourceの「キーセットページング」実装を提供します。データセット全体をクエリし、最初のOFFSET要素を非効率的に破棄するのではなく、キーセットページングは一意の列を使用してクエリの境界を制限します。これは、開発者のメンテナンスコストが高くなるという犠牲を払って、より優れたパフォーマンスを発揮します。

このページングソースが受け入れるqueryProviderコールバックは、beginInclusiveというnull非許容の一意なKeyと、endExclusiveというnull許容の一意なKey?という2つのパラメータを持っています。コアページングクエリの例を以下に示します。

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

キーセットページングで使用されるクエリは、上記のように一意の順序付けを持つ必要があります。

beginInclusiveendExclusiveは、ページ境界として機能する_事前に計算された_キーです。ページ境界を事前に計算するときにページサイズが設定されます。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バージョンをバンドルすることを推奨します。Requery組織は、スタンドアロンライブラリとしてSQLiteの最新ディストリビューションを提供しています。

AndroidX pagingライブラリでは、PagingConfig.initialLoadSizeを使用して、最初のページフェッチのサイズを以降のページフェッチと異なるように設定できます。pageBoundariesProviderコールバックは最初のページフェッチ時に一度だけ呼び出されるため、この機能は避けるべきですPagingConfig.initialLoadSizePagingConfig.pageSizeが一致しない場合、予期せぬページ境界の生成につながります。

このページングソースはジャンプを_サポートしていません_。

このページングソースを作成するには、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拡張関数を使用すべきです。