Paging 라이브러리는 페이징된 데이터의 로드 요청 상태를 추적하고 LoadState
클래스를 통해 노출합니다.
앱은 PagingDataAdapter
에 리스너를 등록하여 현재 상태에 관한 정보를 수신하고 그에 따라 UI를 업데이트할 수 있습니다. 이러한 상태는 UI와 동기화되므로 어댑터에서 제공됩니다.
즉, 페이지 로드가 UI에 적용되면 리스너가 업데이트를 수신합니다.
LoadType
및 데이터 소스 유형(PagingSource
또는 RemoteMediator
)마다 별도의 LoadState
신호가 제공됩니다. 리스너에서 제공하는 CombinedLoadStates
객체는 이러한 모든 신호의 로드 상태에 관한 정보를 제공합니다. 이 세부정보를 사용하여 사용자에게 적절한 로드 표시기를 표시할 수 있습니다.
로드 상태
Paging 라이브러리는 LoadState
객체를 통해 UI에서 사용할 로드 상태를 노출합니다. LoadState
객체는 현재 로드 상태에 따라 다음 세 가지 형식 중 하나를 취합니다.
- 활성 로드 작업이 없고 오류가 없는 경우
LoadState
는LoadState.NotLoading
객체입니다. 이 서브클래스에는 페이지로 나누기의 끝에 도달했는지를 나타내는endOfPaginationReached
속성이 포함됩니다. - 활성 로드 작업이 있는 경우
LoadState
는LoadState.Loading
객체입니다. - 오류가 있는 경우
LoadState
는LoadState.Error
객체입니다.
UI에서 LoadState
를 사용하는 데는 두 가지 방법이 있습니다. 즉, 리스너를 사용하는 방법 또는 특수 목록 어댑터를 사용하여 RecyclerView
목록에 로드 상태를 직접 표시하는 방법입니다.
리스너로 로드 상태에 액세스하기
일반적인 용도로 UI에 로드 상태를 가져오려면 loadStateFlow
스트림 또는 PagingDataAdapter
에서 제공하는 addLoadStateListener()
메서드를 사용하세요. 이러한 메커니즘은 각 로드 유형의 LoadState
동작에 관한 정보를 포함하는 CombinedLoadStates
객체에 액세스할 수 있습니다.
다음 예에서 PagingDataAdapter
는 새로고침 로드의 현재 상태에 따라 다른 UI 구성요소를 표시합니다.
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
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()
함수를 만듭니다. 이 함수는 로드 상태 매개변수에 따라 뷰 가시성을 전환해야 합니다.
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
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()
메서드를 정의합니다. 이러한 메서드는 맞춤 뷰 홀더 인스턴스를 만들고 연결된 로드 상태를 결합합니다.
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
// 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()
메서드를 호출합니다.
Kotlin
pagingAdapter .withLoadStateHeaderAndFooter( header = ExampleLoadStateAdapter(adapter::retry), footer = ExampleLoadStateAdapter(adapter::retry) )
Java
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
참조 문서를 확인하세요.
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
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; });
단, PagingSource
로드 상태만 UI 업데이트와 동기화된다는 점에 유의해야 합니다. refresh
, append
, prepend
속성은 잠재적으로 PagingSource
또는 RemoteMediator
의 로드 상태를 사용할 수 있으므로 UI 업데이트와 동기화되지 않을 수도 있습니다. 이로 인해 새 데이터가 UI에 추가되기 전에 로드가 완료된 것으로 표시되는 UI 문제가 발생할 수 있습니다.
이러한 이유로 편의를 위해 제공되는 접근자는 머리글이나 바닥글에 로드 상태를 표시하는 데는 적합하지만, 다른 사용 사례를 위해서는 PagingSource
또는 RemoteMediator
의 로드 상태에 명시적으로 액세스해야 할 수 있습니다. CombinedLoadStates
는 이러한 용도를 위해 source
와 mediator
속성을 제공합니다. 이러한 속성은 PagingSource
또는 RemoteMediator
별로 LoadState
객체를 포함하는 LoadStates
객체를 각각 노출합니다.
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
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
상태가 될 때까지 기다려야 합니다. 필요한 신호를 제외한 모든 신호를 필터링해야 합니다.
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
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 업데이트가 발생하기 전에 원격 새로고침이 완전히 완료됩니다.
Stream API를 사용하여 이러한 유형의 작업을 할 수 있습니다. 앱은 필요한 로드 이벤트를 지정할 수 있고 적절한 기준이 충족되면 새 데이터를 처리할 수 있습니다.
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 페이징 데이터 로드 및 표시
- 네트워크 및 데이터베이스의 페이지
- Paging 라이브러리 개요