#1 에선 기본적인 Paging3을 구현하였고
여기선 Paging3 에서 LoadStateAdapter
, LoadState
를 알아보려 한다.
( #1에 포함된 코드는 적지 않음. )
시작하기
위의 예제를 만드어보려 한다.
LoadState
sealed class LoadState(
val endOfPaginationReached: Boolean
) {
...
class NotLoading(
endOfPaginationReached: Boolean
) : LoadState(endOfPaginationReached) { ... }
object Loading : LoadState(false) { ... }
class Error(
val error: Throwable
) : LoadState(false) { ... }
}
Paging3 의 로딩 상태는 Loading
, NotLoading
, Error
세 가지로 나뉜다.
Error 일 때는 error 를 가져다 사용할 수 있다.
endOfPaginationReached 값은 로딩 가능 여부를 나타낸다. 해당 값이 true 이면 더 이상 로딩을 못하는 상태이다.
LoadStateAdapter 만들기
class DooolLoadStateAdapter(
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)
}
LoadStateAdapter
는 RecyclerView.Adapter
를 상속받은 클래스이다.
내부에는 LoadState 처리를 위한 코드가 들어있다.
onCreateViewHolder, onBindViewHolder 코드만 작성하면 된다.
retry는 에러시 새로고침을 위한 것이다.
class LoadStateViewHolder(parent: ViewGroup, private val retry: () -> Unit) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_loading, parent, false)) {
private val dataBinding = ItemLoadingBinding.bind(itemView)
fun bind(loadState: LoadState) {
dataBinding.retryButton.setOnClickListener { retry() }
dataBinding.isLoading = loadState is LoadState.Loading
dataBinding.isError = loadState is LoadState.Error
dataBinding.errorMessage = (loadState as? LoadState.Error)?.error?.message ?: ""
dataBinding.executePendingBindings()
}
}
뷰 홀더는 기존 뷰 홀더와 동일하게 RecyclerView.ViewHolder 상속받아 구현하고, 데이터만 LoadState로 받도록 하면 된다.
xml 에선 로딩 중 일 때 프로그래스 바를 보여주고, 에러 일 때는 에러 메시지와 새로고침 버튼을 보여준다.
Paging Source 만들기
class PagingRepository {
suspend fun getPagingData(page: Int): Pair<List<String>, Int?> {
return withContext(Dispatchers.IO) {
delay(500)
if (Random.nextFloat() < 0.2) {
throw Exception("Error $page!!!!!!!!!!!!!!!!!!")
}
Pair(listOf("A $page", "B $page", "C $page"), page + 1)
}
}
}
#1의 코드와 큰 차이는 없고, 로딩 화면을 테스트할 것이기에
withContext(Dispatchers.IO)
로 감싸고 0.5 초의 딜레이를 주었다.
에러 상황도 확인하기 위해 일정 확률로 Exception 도 던졌다.
만약, 첫 페이지부터 Exception 이 발생하면 LoadStateAdapter 가 동작을 안 한다.
( 에러도 안 띄우고 로딩도 안 뜬다. 아무것도 안 한다. 버그인지 의도인지는 모르겠다. )
adapter에 연결하기
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter.withLoadStateHeaderAndFooter(
DooolLoadStateAdapter { adapter.retry() },
DooolLoadStateAdapter { adapter.retry() }
)
}
...
}
LoadStateAdapter를 구현하였으니 PagingDataAdapter에 연결하여야 한다.
연결하는 코드는 withLoadStateFooter
, withLoadStateHeader
, withLoadStateHeaderAndFooter
가 있고, 해당 함수의 결과물로 mergeAdapter 가 나온다.
위의 코드에선 withLoadStateHeaderAndFooter를 사용하였고, 이전 페이지 일 때는 상단에 다음 페이지일 때는 하단에 LoadStateViewHolder가 나타나게 된다.
adapter.addLoadStateListener {
//it.source //it.mediator //it.prepend //it.append //it.refresh
if(it.refresh is LoadState.Error){
adapter.retry()
}
}
PagingDataAdapter의 addLoadStateListener
를 사용하면 LoadState를 가져와서 사용할 수 있다.
값으로 CombinedLoadStates를 넘겨주고 변수로는source
: PagingSource의 prepend, append, refresh 값을 가진 LoadStatesmediator
: RemoteMediator의 prepend, append, refresh 값을 가진 LoadStates // 실험용 API 라 따로 적진 않겠다.prepend
: 앞 페이지 LoadStateappend
: 뒷 페이지 LoadStaterefresh
: 새로고침 LoadState
를 가지고 있다.
refresh는 초기 로딩이나 adapter.refresh 호출 시에 Loading이나 Error 가 내려오고, 그 외에는 NotLoading이다.
위에 코드는 초기 로딩 시에 Error 가 발생하면 retry 하는 코드이다.
'안드로이드' 카테고리의 다른 글
[And] navigation #2 - 딥 링크 (0) | 2020.06.24 |
---|---|
[And] navigation #1 - 기본 사용법 (1) | 2020.06.18 |
안드로이드 Paging 3.0 #1 - 맛보기 (5) | 2020.06.13 |
Dagger - Hilt 간보기 (0) | 2020.06.11 |
Android 에서 미리보기 ( Open Graph ) 만들기 (1) | 2020.03.03 |