〈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