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)
}
}
}
'Kotlin' 카테고리의 다른 글
[Kotlin] DiffUtil과 ListAdapter (0) | 2023.03.06 |
---|---|
[Kotlin] Synchronized와 ReentrantLock이란? (0) | 2023.03.01 |
[Paging] UI에 로드 상태 표시 (0) | 2023.02.20 |
[Paging] UI에서 PagingData 사용 (0) | 2023.02.20 |
[Paging] PagingData를 사용하도록 어댑터 설정 (0) | 2023.02.20 |
댓글