본문 바로가기

안드로이드

안드로이드 paging 3.0 #2 - LoadState

반응형

#1 에선 기본적인 Paging3을 구현하였고
여기선 Paging3 에서 LoadStateAdapter, LoadState 를 알아보려 한다.
( #1에 포함된 코드는 적지 않음. )

시작하기

1) 양 방향 로딩바 , 2) 에러 탐지 및 새로고침

위의 예제를 만드어보려 한다.

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)
}

LoadStateAdapterRecyclerView.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 값을 가진 LoadStates
mediator : RemoteMediator의 prepend, append, refresh 값을 가진 LoadStates // 실험용 API 라 따로 적진 않겠다.
prepend : 앞 페이지 LoadState
append : 뒷 페이지 LoadState
refresh : 새로고침 LoadState

를 가지고 있다.

 

refresh는 초기 로딩이나 adapter.refresh 호출 시에 Loading이나 Error 가 내려오고, 그 외에는 NotLoading이다.

위에 코드는 초기 로딩 시에 Error 가 발생하면 retry 하는 코드이다.

반응형