AsyncPagingDataDiffer

public final class AsyncPagingDataDiffer<T extends Object>


Helper class for mapping a PagingData into a RecyclerView.Adapter.

For simplicity, PagingDataAdapter can often be used in place of this class. AsyncPagingDataDiffer is exposed for complex cases, and where overriding PagingDataAdapter to support paging isn't convenient.

Summary

Public fields

final int

Get the number of items currently presented by this Differ.

final @NonNull Flow<@NonNull CombinedLoadStates>

A hot Flow of CombinedLoadStates that emits a snapshot whenever the loading state of the current PagingData changes.

final @NonNull Flow<Unit>

A hot Flow that emits after the pages presented to the UI are updated, even if the actual items presented don't change.

Public constructors

<T extends Object> AsyncPagingDataDiffer(
    @NonNull DiffUtil.ItemCallback<@NonNull T> diffCallback,
    @NonNull ListUpdateCallback updateCallback,
    @NonNull CoroutineDispatcher mainDispatcher,
    @NonNull CoroutineDispatcher workerDispatcher
)

Public methods

final void
addLoadStateListener(
    @NonNull Function1<@NonNull CombinedLoadStatesUnit> listener
)

Add a CombinedLoadStates listener to observe the loading state of the current PagingData.

final void
addOnPagesUpdatedListener(@NonNull Function0<Unit> listener)

Add a listener which triggers after the pages presented to the UI are updated, even if the actual items presented don't change.

final @Nullable T
getItem(@IntRange(from = 0) int index)

Get the item from the current PagedList at the specified index.

final @Nullable T
peek(@IntRange(from = 0) int index)

Returns the presented item at the specified position, without notifying Paging of the item access that would normally trigger page loads.

final void

Refresh the data presented by this AsyncPagingDataDiffer.

final void

Remove a previously registered CombinedLoadStates listener.

final void

Remove a previously registered listener for new PagingData generations completing initial load and presenting to the UI.

final void

Retry any failed load requests that would result in a LoadState.Error update to this AsyncPagingDataDiffer.

final @NonNull ItemSnapshotList<@NonNull T>

Returns a new ItemSnapshotList representing the currently presented items, including any placeholders if they are enabled.

final void

Present a PagingData until it is invalidated by a call to refresh or PagingSource.invalidate.

final void
submitData(
    @NonNull Lifecycle lifecycle,
    @NonNull PagingData<@NonNull T> pagingData
)

Present a PagingData until it is either invalidated or another call to submitData is made.

Public fields

itemCount

@NonNull
public final int itemCount

Get the number of items currently presented by this Differ. This value can be directly returned to androidx.recyclerview.widget.RecyclerView.Adapter.getItemCount.

Returns
int

Number of items being presented, including placeholders.

loadStateFlow

@NonNull
public final @NonNull Flow<@NonNull CombinedLoadStatesloadStateFlow

A hot Flow of CombinedLoadStates that emits a snapshot whenever the loading state of the current PagingData changes.

This flow is conflated, so it buffers the last update to CombinedLoadStates and immediately delivers the current load states on collection.

val adapter = UserPagingAdapter()
lifecycleScope.launch {
    adapter.loadStateFlow
        .map { it.refresh }
        .distinctUntilChanged()
        .collectLatest {
            // show a retry button outside the list when refresh hits an error
            retryButton.isVisible = it is LoadState.Error

            // swipeRefreshLayout displays whether refresh is occurring
            swipeRefreshLayout.isRefreshing = it is LoadState.Loading

            // show an empty state over the list when loading initially, before items are loaded
            emptyState.isVisible = it is LoadState.Loading && adapter.itemCount == 0
        }
}

onPagesUpdatedFlow

@NonNull
public final @NonNull Flow<UnitonPagesUpdatedFlow

A hot Flow that emits after the pages presented to the UI are updated, even if the actual items presented don't change.

An update is triggered from one of the following:

  • submitData is called and initial load completes, regardless of any differences in the loaded data

  • A Page is inserted

  • A Page is dropped

Note: This is a SharedFlow configured to replay 0 items with a buffer of size 64. If a collector lags behind page updates, it may trigger multiple times for each intermediate update that was presented while your collector was still working. To avoid this behavior, you can conflate this Flow so that you only receive the latest update, which is useful in cases where you are simply updating UI and don't care about tracking the exact number of page updates.

Public constructors

AsyncPagingDataDiffer

public final <T extends Object> AsyncPagingDataDiffer(
    @NonNull DiffUtil.ItemCallback<@NonNull T> diffCallback,
    @NonNull ListUpdateCallback updateCallback,
    @NonNull CoroutineDispatcher mainDispatcher,
    @NonNull CoroutineDispatcher workerDispatcher
)

Public methods

addLoadStateListener

@NonNull
public final void addLoadStateListener(
    @NonNull Function1<@NonNull CombinedLoadStatesUnit> listener
)

Add a CombinedLoadStates listener to observe the loading state of the current PagingData.

As new PagingData generations are submitted and displayed, the listener will be notified to reflect the current CombinedLoadStates.

val adapter = UserPagingAdapter()
adapter.addLoadStateListener {
    // show a retry button outside the list when refresh hits an error
    retryButton.isVisible = it.refresh is LoadState.Error

    // swipeRefreshLayout displays whether refresh is occurring
    swipeRefreshLayout.isRefreshing = it.refresh is LoadState.Loading

    // show an empty state over the list when loading initially, before items are loaded
    emptyState.isVisible = it.refresh is LoadState.Loading && adapter.itemCount == 0
}
Parameters
@NonNull Function1<@NonNull CombinedLoadStatesUnit> listener

LoadStates listener to receive updates.

addOnPagesUpdatedListener

@NonNull
public final void addOnPagesUpdatedListener(@NonNull Function0<Unit> listener)

Add a listener which triggers after the pages presented to the UI are updated, even if the actual items presented don't change.

An update is triggered from one of the following:

  • submitData is called and initial load completes, regardless of any differences in the loaded data

  • A Page is inserted

  • A Page is dropped

Parameters
@NonNull Function0<Unit> listener

called after pages presented are updated.

getItem

@Nullable
public final T getItem(@IntRange(from = 0) int index)

Get the item from the current PagedList at the specified index.

Note that this operates on both loaded items and null padding within the PagedList.

Parameters
@IntRange(from = 0) int index

Index of item to get, must be >= 0, and < itemCount

Returns
T

The item, or null, if a null placeholder is at the specified position.

peek

@Nullable
public final T peek(@IntRange(from = 0) int index)

Returns the presented item at the specified position, without notifying Paging of the item access that would normally trigger page loads.

Parameters
@IntRange(from = 0) int index

Index of the presented item to return, including placeholders.

Returns
T

The presented item at position index, null if it is a placeholder

refresh

@NonNull
public final void refresh()

Refresh the data presented by this AsyncPagingDataDiffer.

refresh triggers the creation of a new PagingData with a new instance of PagingSource to represent an updated snapshot of the backing dataset. If a RemoteMediator is set, calling refresh will also trigger a call to RemoteMediator.load with LoadType to allow RemoteMediator to check for updates to the dataset backing PagingSource.

Note: This API is intended for UI-driven refresh signals, such as swipe-to-refresh. Invalidation due repository-layer signals, such as DB-updates, should instead use PagingSource.invalidate.

class MyActivity : AppCompatActivity() {
    private lateinit var binding: MyActivityBinding
    private val pagingAdapter = UserPagingAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = MyActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.recyclerView.adapter = pagingAdapter
        pagingAdapter.addLoadStateListener { loadStates ->
            binding.swipeRefreshLayout.isRefreshing = loadStates.refresh is LoadState.Loading
        }

        binding.swipeRefreshLayout.setOnRefreshListener {
            pagingAdapter.refresh()
        }
    }
}
See also
invalidate

removeLoadStateListener

@NonNull
public final void removeLoadStateListener(
    @NonNull Function1<@NonNull CombinedLoadStatesUnit> listener
)

Remove a previously registered CombinedLoadStates listener.

Parameters
@NonNull Function1<@NonNull CombinedLoadStatesUnit> listener

Previously registered listener.

removeOnPagesUpdatedListener

@NonNull
public final void removeOnPagesUpdatedListener(@NonNull Function0<Unit> listener)

Remove a previously registered listener for new PagingData generations completing initial load and presenting to the UI.

Parameters
@NonNull Function0<Unit> listener

Previously registered listener.

retry

@NonNull
public final void retry()

Retry any failed load requests that would result in a LoadState.Error update to this AsyncPagingDataDiffer.

Unlike refresh, this does not invalidate PagingSource, it only retries failed loads within the same generation of PagingData.

LoadState.Error can be generated from two types of load requests:

snapshot

@NonNull
public final ItemSnapshotList<@NonNull T> snapshot()

Returns a new ItemSnapshotList representing the currently presented items, including any placeholders if they are enabled.

submitData

@NonNull
public final void submitData(@NonNull PagingData<@NonNull T> pagingData)

Present a PagingData until it is invalidated by a call to refresh or PagingSource.invalidate.

This method is typically used when collecting from a Flow produced by Pager. For RxJava or LiveData support, use the non-suspending overload of submitData, which accepts a Lifecycle.

Note: This method suspends while it is actively presenting page loads from a PagingData, until the PagingData is invalidated. Although cancellation will propagate to this call automatically, collecting from a Pager.flow with the intention of presenting the most up-to-date representation of your backing dataset should typically be done using collectLatest.

See also
Pager

submitData

@NonNull
public final void submitData(
    @NonNull Lifecycle lifecycle,
    @NonNull PagingData<@NonNull T> pagingData
)

Present a PagingData until it is either invalidated or another call to submitData is made.

This method is typically used when observing a RxJava or LiveData stream produced by Pager. For Flow support, use the suspending overload of submitData, which automates cancellation via CoroutineScope instead of relying of Lifecycle.

See also
submitData
Pager