Paging ライブラリは、ページング データの読み込みリクエストの状態をトラッキングし、LoadState
クラスを通じて公開します。アプリは PagingDataAdapter
にリスナーを登録して現在の状態に関する情報を受信し、それに応じて UI を更新できます。これらの状態は、UI と同期しているため、アダプターから提供されます。つまり、ページ読み込みが UI に適用されると、リスナーは更新情報を受信します。
個々の LoadState
シグナルは、LoadType
とデータソース タイプ(PagingSource
または RemoteMediator
)ごとに提供されます。リスナーから渡される CombinedLoadStates
オブジェクトは、これらすべてのシグナルから届いた読み込み状態に関する情報を提供します。この詳細な情報を使用して、適切な読み込みインジケーターをユーザーに表示できます。
読み込み状態
Paging ライブラリは、LoadState
オブジェクトを介して、UI で使用される読み込み状態を公開します。LoadState
オブジェクトは、現在の読み込み状態に応じて次の 3 つの形態のいずれかを取ります。
- アクティブな読み込みオペレーションが存在せず、エラーがない場合、
LoadState
はLoadState.NotLoading
オブジェクトになります。このサブクラスには、ページネーションが終わりに達したかどうかを示すendOfPaginationReached
プロパティも含まれています。 - アクティブな読み込みオペレーションが存在する場合、
LoadState
はLoadState.Loading
オブジェクトになります。 - エラーがある場合、
LoadState
はLoadState.Error
オブジェクトになります。
UI で LoadState
を使用するには、2 つの方法があります。つまり、リスナーを使用する方法と、特別なリスト アダプターを使って読み込み状態を直接 RecyclerView
リストに表示する方法です。
リスナーを使用して読み込み状態にアクセスする
UI で一般的に使用される読み込み状態を取得するには、loadStateFlow
ストリームを使用するか、PagingDataAdapter
によって提供される addLoadStateListener()
メソッドを使用します。これらのメカニズムにより、各読み込みタイプの LoadState
動作に関する情報を含む CombinedLoadStates
オブジェクトへのアクセスが提供されます。
次の例の PagingDataAdapter
は、更新読み込みの現在の状態に応じて、異なる UI コンポーネントを表示します。
// 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
}
}
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);
});
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);
});
CombinedLoadStates
について詳しくは、その他の読み込み状態の情報にアクセスするをご覧ください。
アダプターで読み込み状態を表示する
Paging ライブラリには、LoadStateAdapter
という名前の別のリスト アダプターがあります。これは、表示されるページング データのリストに読み込み状態を直接表示するために使用します。このアダプターは、リストの現在の読み込み状態へのアクセスを提供します。これを、情報を表示するカスタム ビューホルダーに渡すことができます。
まず、画面上の読み込みビューとエラービューへの参照を保持するビューホルダー クラスを作成します。LoadState
をパラメータとして受け取る bind()
関数を作成します。この関数は、読み込み状態パラメータに基づいて、ビューの表示を切り替えます。
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
}
}
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);
}
}
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);
}
}
次に、LoadStateAdapter
を実装するクラスを作成し、onCreateViewHolder()
メソッドと onBindViewHolder()
メソッドを定義します。これらのメソッドは、カスタム ビューホルダーのインスタンスを作成し、関連する読み込み状態をバインドします。
// 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)
}
// 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);
}
}
// 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);
}
}
読み込み状態をヘッダーまたはフッターとして表示する
ヘッダーとフッターに読み込みの進行状況を表示するには、PagingDataAdapter
オブジェクトから withLoadStateHeaderAndFooter()
メソッドを呼び出します。
pagingAdapter
.withLoadStateHeaderAndFooter(
header = ExampleLoadStateAdapter(adapter::retry),
footer = ExampleLoadStateAdapter(adapter::retry)
)
pagingAdapter
.withLoadStateHeaderAndFooter(
new ExampleLoadStateAdapter(pagingAdapter::retry),
new ExampleLoadStateAdapter(pagingAdapter::retry));
pagingAdapter
.withLoadStateHeaderAndFooter(
new ExampleLoadStateAdapter(pagingAdapter::retry),
new ExampleLoadStateAdapter(pagingAdapter::retry));
代わりに、RecyclerView
リストでヘッダーのみ、またはフッターのみに読み込み状態を表示したい場合は、withLoadStateHeader()
または withLoadStateFooter()
を呼び出すこともできます。
その他の読み込み状態の情報にアクセスする
PagingDataAdapter
からの CombinedLoadStates
オブジェクトは、PagingSource
実装と、RemoteMediator
実装(存在する場合)の読み込み状態に関する情報を提供します。
簡便化のため、CombinedLoadStates
からの refresh
、append
および prepend
プロパティを使用して、適切な読み込みタイプに応じた LoadState
オブジェクトにアクセスできます。一般的に、これらのプロパティは、RemoteMediator
実装からの読み込み状態が存在する場合はそれに従います。そうでない場合は、PagingSource
実装からの適切な読み込み状態を保持します。基盤となるロジックについて詳しくは、CombinedLoadStates
のリファレンス ドキュメントをご覧ください。
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
}
}
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;
});
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;
});
なお、UI 更新との同期が保証されるのは、PagingSource
読み込み状態のみであることにご注意ください。refresh
、append
、および prepend
プロパティは、PagingSource
または RemoteMediator
のいずれかから読み込み状態を取得する可能性があるため、UI 更新との同期は保証されません。そのため、新しいデータが UI に追加される前に、UI で読み込みが完了したように見える問題が発生する可能性があります。
したがって、コンビニエンス アクセサは読み込み状態をヘッダーまたはフッターに表示する場合は適切に機能しますが、他のユースケースでは、明確に PagingSource
または RemoteMediator
のいずれかから読み込み状態にアクセスする必要があります。CombinedLoadStates
は、この目的のために source
プロパティと mediator
プロパティを提供します。これらのプロパティは、PagingSource
または RemoteMediator
用の LoadState
オブジェクトを含む LoadStates
オブジェクトをそれぞれ公開します。
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
}
}
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;
});
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;
});
LoadState のチェーン演算子
CombinedLoadStates
オブジェクトは読み込み状態のあらゆる変更へのアクセスを提供するので、特定のイベントに基づいて読み込み状態ストリームをフィルタすることが重要です。これにより、適切なタイミングで UI を更新して、ぎこちない動作や不要な UI 更新を回避できます。
たとえば、初期データの読み込みが完了した後で初めて空のビューを表示したいとします。このユースケースでは、データ更新読み込みが開始されたことを確認してから、NotLoading
状態を待機して更新の完了を確認することが求められます。必要なものを除くすべてのシグナルをフィルタで除外する必要があります。
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) }
}
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);
});
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);
}
});
この例では、更新の読み込み状態が更新されるまで待機しますが、状態が NotLoading
の場合にのみトリガーします。これにより、UI 更新が発生する前にリモート更新が完全に終了することが保証されます。
ストリーム API を使用すると、このようなオペレーションが可能になります。アプリでは、アプリに必要な読み込みイベントを指定して、適切な条件が満たされたときに新しいデータを処理できます。
現在、おすすめはありません。
Google アカウントにログインしてください。