본문 바로가기
Kotlin

[Paging 고급] 데이터 소스 정의

by 명훈스토리 2023. 2. 20.
SMALL

PagingSource 구현에서는 데이터 소스를 정의하고 이 소스에서 데이터를 가져오는 방법을 정의한다. PagingData 객체는 사용자가 RecyclerView에서 스크롤할 때 생성되는 힌트가 로드되면 PagingSource에서 데이터를 쿼리한다.

 

현재 GithubRepository는 추가된 후 Paging 라이브러리에서 처리되는 데이터 소스와 관련하여 다양한 작업을 실행한다.

  • 여러 요청이 동시에 트리거되지 않도록 GithubService에서 데이터를 로드한다.
  • 검색된 데이터의 메모리 내 캐시를 유지한다.
  • 요청될 페이지를 추적한다.

PagingSource를 빌드하려면 다음 항목을 정의해야 한다.

  • 페이징 키의 유형 : 여기서는 Github API에서 페이지에 1을 기반으로 하는 색인 번호를 사용하므로 유형이 Int이다.
  • 로드된 데이터의 유형 : 여기서는 Repo 항목을 로드한다.
  • 데이터를 가져오는 위치 : GithubService에서 데이터를 가져온다. 데이터 소스는 특정 쿼리에 한정되므로 쿼리 정보도 GithubService에 전달해야 한다.
class GithubPagingSource(
        private val service: GithubService,
        private val query: String
) : PagingSource<Int, Repo>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        TODO("Not yet implemented")
    }
   override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
        TODO("Not yet implemented")
    }

}

PagingSource에서 두 가지 함수(load() 및 getRefreshKey())를 구현해야 한다.

 

사용자가 스크롤할 때 표시할 더 많은 데이터를 비동기식으로 가져오기 위해 Paging 라이브러리에서 load() 함수를 호출한다. LoadParams 객체에는 다음 항목을 포함하여 로드 작업과 관련된 정보가 저장된다.

  • 로드할 페이지의 키 : 로드가 처음 호출되는 경우에는 LoadParams.key가 null이다. 여기서는 초기 페이지 키를 정의해야 한다. 이 프로젝트에서는 이 키가 초기 페이지 키이므로 GITHUB_STARTING_PAGE_INDEX 상수를 GithubRepository에서 PagingSource 구현으로 이동해야 한다.
  • 로드 크기 : 로드 요청된 항목의 수

로드 함수는 LoadResult를 반환한다. LoadResult가 다음 유형 중 하나를 취할 수 있으므로 이 앱에서는 RepoSearchResult 대신 사용된다.

  • LoadResult.Page : 로드에 성공한 경우
  • LoadResult.Error : 오류가 발생한 경우

LoadResult.Page를 구성할 때 상응하는 방향으로 목록을 로드할 수 없는 경우 nextKey 또는 prevKey에 null을 전달한다. 예를 들어 여기서는 네트워크 응답에 성공했짐나 목록이 비어 있는 경우에는 로드할 데이터가 없는 것으로 간주할 수 있다. 따라서 nextKey가 null일 수 있다.

 

이 모든 정보를 바탕으로 load() 함수를 구현할 수 있다.

 

다음으로 getRefreshKey()를 구현해야 한다. 새로고침 키는 PagingSource.load()의 후속 새로고침 호출에 사용된다. 첫 번째 호출은 Pager에 의해 제공되는 initalKey를 사용하는 초기 로드이다. 새로고침은 스와이프하여 새로고침하거나 데이터베이스 업데이트, 구성 변경, 프로세스 중단 등으로 인해 무효화되어 Paging 라이브러리가 현재 목록을 대체할 새 데이터를 로드하려고 할 때마다 발생한다. 일반적으로 후속 새로고침 호출은 가장 최근데 액세스한 인덱스를 나타내는 PagingState.anchorPosition 주변 데이터의 로드를 다시 시작하려고 한다.

 

GithubPagingSource 구현은 다음과 같다.

 

// GitHub page API is 1 based: https://developer.github.com/v3/#pagination
private const val GITHUB_STARTING_PAGE_INDEX = 1

class GithubPagingSource(
        private val service: GithubService,
        private val query: String
) : PagingSource<Int, Repo>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
        val apiQuery = query + IN_QUALIFIER
        return try {
            val response = service.searchRepos(apiQuery, position, params.loadSize)
            val repos = response.items
            val nextKey = if (repos.isEmpty()) {
                null
            } else {
                // initial load size = 3 * NETWORK_PAGE_SIZE
                // ensure we're not requesting duplicating items, at the 2nd request
                position + (params.loadSize / NETWORK_PAGE_SIZE)
            }
            LoadResult.Page(
                    data = repos,
                    prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                    nextKey = nextKey
            )
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }
    // The refresh key is used for subsequent refresh calls to PagingSource.load after the initial load
    override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
        // We need to get the previous key (or next key if previous is null) of the page
        // that was closest to the most recently accessed index.
        // Anchor position is the most recently accessed index
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

}
LIST

댓글