The Paging library tracks the state of load requests for paged data and exposes
it through the [`LoadState`](https://developer.android.com/reference/kotlin/androidx/paging/LoadState) class.
Your app can register a listener with the
[`PagingDataAdapter`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter) to
receive information about the current state and update the UI accordingly. These
states are provided from the adapter because they are synchronous with the UI.
This means that your listener receives updates when the page load has been
applied to the UI.

A separate `LoadState` signal is provided for each
[`LoadType`](https://developer.android.com/reference/kotlin/androidx/paging/LoadType) and data source type
(either [`PagingSource`](https://developer.android.com/reference/kotlin/androidx/paging/PagingSource) or
[`RemoteMediator`](https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator)). The
[`CombinedLoadStates`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates)
object provided by the listener provides information about the loading state
from all of these signals. You can use this detailed information to display the
appropriate loading indicators to your users.

## Loading states

The Paging library exposes the loading state for use in the UI through the
`LoadState` object. `LoadState` objects take one of three forms depending on the
current loading state:

- If there is no active load operation and no error, then `LoadState` is a [`LoadState.NotLoading`](https://developer.android.com/reference/kotlin/androidx/paging/LoadState.NotLoading) object. This subclass also includes the [`endOfPaginationReached`](https://developer.android.com/reference/kotlin/androidx/paging/LoadState#endOfPaginationReached()) property, which indicates whether the end of pagination has been reached.
- If there is an active load operation, then `LoadState` is a [`LoadState.Loading`](https://developer.android.com/reference/kotlin/androidx/paging/LoadState.Loading) object.
- If there is an error, then `LoadState` is a [`LoadState.Error`](https://developer.android.com/reference/kotlin/androidx/paging/LoadState.Error) object.

There are two ways to use `LoadState` in your UI: using a listener, or using a
special list adapter to present the loading state directly in the
[`RecyclerView`](https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/RecyclerView)
list.

## Access the loading state with a listener

To get the loading state for general use in your UI, use the
[`loadStateFlow`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#loadstateflow)
stream or the
[`addLoadStateListener()`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#addloadstatelistener)
method provided by your `PagingDataAdapter`. These mechanisms provide access to
a `CombinedLoadStates` object that includes information about the `LoadState`
behavior for each load type.

In the following example, the `PagingDataAdapter` displays different UI
components depending on the current state of the refresh load:  

### Kotlin

```kotlin
// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    progressBar.isVisible = loadStates.refresh is LoadState.Loading
    retry.isVisible = loadState.refresh !is LoadState.Loading
    errorMsg.isVisible = loadState.refresh is LoadState.Error
  }
}
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});
```
| **Note:** Updates from `loadStateFlow` and `addLoadStateListener()` are guaranteed to be synchronous with updates to the UI. This means that if you receive a `LoadState.NotLoading` object, then you can be sure that loading has completed and the UI has been updated accordingly.

For more information on `CombinedLoadStates`, see [Access additional loading
state information](https://developer.android.com/topic/libraries/architecture/paging/load-state#additional-info).

## Present the loading state with an adapter

The Paging library provides another list adapter called
[`LoadStateAdapter`](https://developer.android.com/reference/kotlin/androidx/paging/LoadStateAdapter) for the
purpose of presenting the loading state directly in the displayed list of paged
data. This adapter provides access to the current load state of the list, which
you can pass to a custom view holder that displays the information.

First, create a view holder class that keeps references to the loading and error
views on your screen. Create a `bind()` function that accepts a `LoadState` as a
parameter. This function should toggle the view visibility based on the load
state parameter:  

### Kotlin

```kotlin
class LoadStateViewHolder(
  parent: ViewGroup,
  retry: () -> Unit
) : RecyclerView.ViewHolder(
  LayoutInflater.from(parent.context)
    .inflate(R.layout.load_state_item, parent, false)
) {
  private val binding = LoadStateItemBinding.bind(itemView)
  private val progressBar: ProgressBar = binding.progressBar
  private val errorMsg: TextView = binding.errorMsg
  private val retry: Button = binding.retryButton
    .also {
      it.setOnClickListener { retry() }
    }

  fun bind(loadState: LoadState) {
    if (loadState is LoadState.Error) {
      errorMsg.text = loadState.error.localizedMessage
    }

    progressBar.isVisible = loadState is LoadState.Loading
    retry.isVisible = loadState is LoadState.Error
    errorMsg.isVisible = loadState is LoadState.Error
  }
}
```

### Java

```java
class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}
```

### Java

```java
class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}
```

Next, create a class that implements `LoadStateAdapter`, and define the
[`onCreateViewHolder()`](https://developer.android.com/reference/kotlin/androidx/paging/LoadStateAdapter#onCreateViewHolder(android.view.ViewGroup,androidx.paging.LoadState))
and
[`onBindViewHolder()`](https://developer.android.com/reference/kotlin/androidx/paging/LoadStateAdapter#onBindViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder,androidx.paging.LoadState))
methods. These methods create an instance of your custom view holder and bind
the associated load state.  

### Kotlin

```kotlin
// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
  private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {

  override fun onCreateViewHolder(
    parent: ViewGroup,
    loadState: LoadState
  ) = LoadStateViewHolder(parent, retry)

  override fun onBindViewHolder(
    holder: LoadStateViewHolder,
    loadState: LoadState
  ) = holder.bind(loadState)
}
```

### Java

```java
// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}
```

### Java

```java
// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}
```

### Display the loading state as a header or footer

To display the loading progress in a header and a footer, call the
[`withLoadStateHeaderAndFooter()`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#withloadstateheaderandfooter)
method from your `PagingDataAdapter` object:  

### Kotlin

```kotlin
pagingAdapter
  .withLoadStateHeaderAndFooter(
    header = ExampleLoadStateAdapter(adapter::retry),
    footer = ExampleLoadStateAdapter(adapter::retry)
  )
```

### Java

```java
pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));
```

### Java

```java
pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));
```

You can instead call
[`withLoadStateHeader()`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#withloadstateheader)
or
[`withLoadStateFooter()`](https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter#withloadstatefooter)
if you want the `RecyclerView` list to display the loading state only in the
header or only in the footer.

## Access additional loading state information

The `CombinedLoadStates` object from `PagingDataAdapter` provides information on
the load states for your `PagingSource` implementation and also for your
`RemoteMediator` implementation, if one exists.

For convenience, you can use the
[`refresh`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates#refresh()),
[`append`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates#append()), and
[`prepend`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates#prepend())
properties from `CombinedLoadStates` to access a `LoadState` object for the
appropriate load type. These properties generally defer to the load state from
the `RemoteMediator` implementation if one exists; otherwise, they contain the
appropriate load state from the `PagingSource` implementation. For more detailed
information on the underlying logic, see the reference documentation for
[`CombinedLoadStates`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates).  

### Kotlin

```kotlin
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Observe refresh load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    refreshLoadState: LoadState = loadStates.refresh
    // Observe prepend load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    prependLoadState: LoadState = loadStates.prepend
    // Observe append load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    appendLoadState: LoadState = loadStates.append
  }
}
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});
```

However, it is important to remember that only the `PagingSource` load states
are guaranteed to be synchronous with UI updates. Because the `refresh`,
`append`, and `prepend` properties can potentially take the load state from
either `PagingSource` or `RemoteMediator`, they are not guaranteed to be
synchronous with UI updates. This can cause UI issues where the loading appears
to finish before any of the new data has been added to the UI.

For this reason, the convenience accessors work well for displaying the load
state in a header or footer, but for other use cases you might need to
specifically access the load state from either `PagingSource` or
`RemoteMediator`. `CombinedLoadStates` provides the
[`source`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates#source()) and
[`mediator`](https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates#mediator())
properties for this purpose. These properties each expose a
[`LoadStates`](https://developer.android.com/reference/kotlin/androidx/paging/LoadStates) object that
contains the `LoadState` objects for `PagingSource` or `RemoteMediator`
respectively:  

### Kotlin

```kotlin
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Directly access the RemoteMediator refresh load state.
    mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh
    // Directly access the RemoteMediator append load state.
    mediatorAppendLoadState: LoadState? = loadStates.mediator.append
    // Directly access the RemoteMediator prepend load state.
    mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend
    // Directly access the PagingSource refresh load state.
    sourceRefreshLoadState: LoadState = loadStates.source.refresh
    // Directly access the PagingSource append load state.
    sourceAppendLoadState: LoadState = loadStates.source.append
    // Directly access the PagingSource prepend load state.
    sourcePrependLoadState: LoadState = loadStates.source.prepend
  }
}
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});
```

### Java

```java
pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});
```

## Chain operators on LoadState

Because the `CombinedLoadStates` object provides access to all changes in
load state, it is important to filter the load state stream based on specific
events. This ensures that you update your UI at the appropriate time to avoid
stutters and unnecessary UI updates.

For example, suppose that you want to display an empty view, but only after the
initial data load completes. This use case requires that you verify that a data
refresh load has started, then wait for the `NotLoading` state to confirm that
the refresh has completed. You must filter out all signals except for the ones
you need:  

### Kotlin

```kotlin
lifecycleScope.launchWhenCreated {
  adapter.loadStateFlow
    // Only emit when REFRESH LoadState for RemoteMediator changes.
    .distinctUntilChangedBy { it.refresh }
    // Only react to cases where REFRESH completes, such as NotLoading.
    .filter { it.refresh is LoadState.NotLoading }
    // Scroll to top is synchronous with UI updates, even if remote load was
    // triggered.
    .collect { binding.list.scrollToPosition(0) }
}
```

### Java

```java
PublishSubject<CombinedLoadStates> subject = PublishSubject.create();
Disposable disposable =
  subject.distinctUntilChanged(CombinedLoadStates::getRefresh)
  .filter(
    combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading)
  .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0));

pagingAdapter.addLoadStateListener(loadStates -> {
  subject.onNext(loadStates);
});
```

### Java

```java
LiveData<CombinedLoadStates> liveData = new MutableLiveData<>();
LiveData<LoadState> refreshLiveData =
  Transformations.map(liveData, CombinedLoadStates::getRefresh);
LiveData<LoadState> distinctLiveData =
  Transformations.distinctUntilChanged(refreshLiveData);

distinctLiveData.observeForever(loadState -> {
  if (loadState instanceof LoadState.NotLoading) {
    binding.list.scrollToPosition(0);
  }
});
```

This example waits until the refresh load state is updated, but only triggers
when the state is `NotLoading`. This ensures that the remote refresh has fully
finished before any UI updates happen.

Stream APIs make this type of operation possible. Your app can specify the load
events it needs and handle the new data when the appropriate criteria are met.

## Recommended for you

- Note: link text is displayed when JavaScript is off
- [Load and display paged data](https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data)
- [Page from network and database](https://developer.android.com/topic/libraries/architecture/paging/v3-network-db)
- [Paging library overview](https://developer.android.com/topic/libraries/architecture/paging/v3-overview)