Continuity is The Father of Success

Androidアプリとかゲームとか。毎日続けてるものについて。

Pagingライブラリ3.0で入りそうな変更の話

ここのところ諸事情があってPagingライブラリの開発中コードを見に行っていたのですが、面白そうな変更が入っていたので技術メモ。 まだalphaにもなっていないのでまだまだ変更はあるだろうし、リリースされないかもしれません。

PagedListがPagedDataへ

PagedList *1PagingData *2 に置き換えになります。 大抵の場合 PagingListPagedData も直接いじることはありません。 つまりRepository層でPagingライブラリを利用して作成したらRecyclerViewのAdapterにそのまま渡すだけ、と考えられるため実装上では影響はほぼありません。

@Deprecated("PagedList is deprecated and has been replaced by PagingData")
abstract class PagedList<T : Any> internal constructor(
    /**
     * The [PagingSource] that provides data to this [PagedList].
     *
     * @suppress
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    open val pagingSource: PagingSource<*, T>,
    internal val storage: PagedStorage<T>,
    /**
     * Return the Config used to construct this PagedList.
     *
     * @return the Config of this PagedList
     */
    val config: Config
) : AbstractList<T>() {
class PagingData<T : Any> internal constructor(
    internal val flow: Flow<PageEvent<T>>,
    internal val receiver: UiReceiver
) {

すんごい余談になりますが、今年のDroidKaigiへ出したネタの中で一番自信があったのが「Pagingライブラリと組み合わせるならPagedListAdapterとGroupieとEpoxyのどれがいいの?」という話でした。 RecyclerView.Adapter を独自に拡張してPagingライブラリへの対応を行っていたりする場合、ライブラリ側で対応するためにはKotlin Coroutinesをライブラリの依存関係に追加することが必要になりそうです。 今回の更新により、結論は PagingDataAdapter を使う話になりそうですね。なんてこったい。

PagedListAdapterがPagingDataAdapterへ

PagedListAdapter *3PagingDataAdapter *4 に変わっています。

@Deprecated(
    message = "PagedListAdapter is deprecated and has been replaced by PagingDataAdapter",
    replaceWith = ReplaceWith(
        "PagingDataAdapter<T, VH>",
        "androidx.paging.PagingDataAdapter"
    )
)
abstract class PagedListAdapter<T : Any, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH> {

    /**
     * Creates a [PagedListAdapter] with default threading and
     * [androidx.recyclerview.widget.ListUpdateCallback].
     *
     * Convenience for [PagedListAdapter], which uses default threading behavior.
     *
     * @param diffCallback The [DiffUtil.ItemCallback] instance to
     * compare items in the list.
     */
    protected constructor(diffCallback: DiffUtil.ItemCallback<T>) {
        @Suppress("DEPRECATION")
        differ = AsyncPagedListDiffer(this, diffCallback)
        differ.addPagedListListener(listener)
    }
    protected constructor(config: AsyncDifferConfig<T>) {
        @Suppress("DEPRECATION")
        differ = AsyncPagedListDiffer(AdapterListUpdateCallback(this), config)
        differ.addPagedListListener(listener)
    }
abstract class PagingDataAdapter<T : Any, VH : RecyclerView.ViewHolder>(
    diffCallback: DiffUtil.ItemCallback<T>,
    mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
    workerDispatcher: CoroutineDispatcher = Dispatchers.Default
) : RecyclerView.Adapter<VH>() {

AsyncDifferConfig をセットしていたケースでは修正が必要になるっぽいのですが、大抵の場合 DiffUtil.ItemCallback を使うので置き換えは簡単そうな見込みです。 一方で当然のように Dispatchers.Main がdefault valueとして指定されているなど、構造がマルっと変わっています。 Kotlin CoroutinesがPagingライブラリに導入されていく話はKotlin愛好会の技術同人誌*5に寄稿しているので、興味があればお手に取ってみてください。(露骨な宣伝)

これまた余談なのですが MergeAdapter が入っていたりと、いくつか実装の参考にできそうなところもあります。

    /**
     * Create a [MergeAdapter] with the provided [LoadStateAdapter]s displaying the
     * [LoadType.START] and [LoadType.END] [LoadState]s as list items at the start and end
     * respectively.
     *
     * @see LoadStateAdapter
     * @see withLoadStateHeader
     * @see withLoadStateFooter
     */
    fun withLoadStateHeaderAndFooter(
        header: LoadStateAdapter<*>,
        footer: LoadStateAdapter<*>
    ): MergeAdapter {
        addLoadStateListener { loadType, loadState ->
            if (loadType == LoadType.START) {
                header.loadState = loadState
            } else if (loadType == LoadType.END) {
                footer.loadState = loadState
            }
        }
        return MergeAdapter(header, this, footer)
    }

カスタムのAdapterを作るときに、参考にしていけそうです。たのしみ。

PageKeyed / ItemKeyed / PositionalDataSourceがPagingSourceへ

DataSource の実装である PageKeyedDataSource *6 , ItemKeyedDataSource *7PositionalDataSource *8PagingSource *9 に置き換えられます。

この影響は loadInitial loadBefore loadAfter の各メソッドで書き分けられていた処理が load メソッドにまとめられ、 LoadType *10 で処理を分ける形に変わります。

enum class LoadType {
    /**
     * [PagingData] content being refreshed, which can be a result of [PagingSource]
     * invalidation, refresh that may contain content updates, or the initial load.
     */
    REFRESH,
    /**
     * Load at the start of a [PagingData].
     */
    START,
    /**
     * Load at the end of a [PagingData].
     */
    END
}

小さな変更ですが、Pagingライブラリの更新で書き換えが発生する個所となります。 loadsuspend になることと合わせて、簡単な更新の準備をしておいた方が良さそうです。

おわりに

正式リリースが楽しみです。 首を長くして待ちたいなと思います。

*1:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/PagedList.kt

*2:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/PagingData.kt

*3:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt

*4:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt

*5:Love Swift & Kotlin #2

*6:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt

*7:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt

*8:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt

*9:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt

*10:https://android.googlesource.com/platform/frameworks/support/+/2a9a199e0b4b2967f3e7446c29bd97ec2d7510f9/paging/common/src/main/kotlin/androidx/paging/LoadType.kt