Jetpack Paging3 - 2. DataSource 정의하기

12048 단어 jetpackPaging3Paging3

이번 포스팅부터는 Android Developers에서 제공하는 Paging3 Codelab을 진행하면서 공부한 내용이 포함됩니다.

⚜️ PagingSource

DataSource를 정의하기 위해 구현하는 PagingSource 클래스와 사용되는 메서드에 대해 알아봅니다.

DataSource를 식별하기 위해 먼저, PagingSource를 정의해야 합니다. PagingSource에는 대표적으로 아래 항목들을 정의합니다

  • 페이징 key의 유형

  • 로드된 Data의 유형

  • 데이터를 가져오는 위치

PagingSource<Key, Value>에는 KeyValue 를 지정해야 합니다. 여기서 Key는 데이터를 로드하는데 사용되는 식별자 유형을 정의하며 Value는 데이터 자체의 유형입니다. 즉, DataSource를 정의하기 위해 Key와 반환 Data를 Generic으로 받습니다.

Codelab에서는 GitHub API를 사용하여 Repository를 페이징하는 실습을 진행합니다. GitHub API에서 페이지 1을 기반으로 하는 번호를 사용하기 때문에 페이징 Key의 유형은 Int입니다. 로드된 데이터의 유형으로는 Network Response에 해당하는 Repo data class를 사용합니다.

추가로 데이터를 가져오기 위한 API 인터페이스를 생성자로 제공하게 됩니다. Codelab에서는 해당 인터페이스인 GithubService를 생성자로 전달합니다.


class GithubPagingSource(
    private val service: GithubService,
    private val query: String
) : PagingSource<Int, Repo>() {
    override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
    	@TODO
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
    	@TODO
    }
}

✅ load()

load() 함수는 실제 데이터를 가져오는 로직을 구현하는 함수입니다. 간단하게, 사용자가 데이터 열람을 위해 스크롤 시 표시할 데이터를 비동기식으로 가져오기 위해 사용된다고 볼 수 있습니다.

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 {
        	position + (params.loadSize / NETWORK_PAGE_SIZE)
        }

		LoadResult.Page(
        	data = repos,
            prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
            nextKey = nextKey
        )
    } catch (e: IOException) {
    	return LoadResult.Error(e)
    } catch (e: HttpException) {
    	return LoadResult.Error(e)
    }
}

parameter로 LoadParams 객체가 전달되는데 아래 항목들이 포함됩니다.

  • LoadParams.key
    로드할 페이지의 Key에 해당합니다. 처음 로드될 경우 LoadParams.keynull에 해당됩니다. 초기 페이지 키를 직접 지정할 수 있습니다.

  • LoadParams.loadSize
    로드할 데이터 항목의 갯수를 정의합니다.

load()함수는 데이터 로드 결과에 대한 LoadResult sealed class를 반환합니다. LoadResult에 대한 예외 처리가 필요합니다.

  • LoadResult.Page
    데이터 로드 성공 시

  • LoadResult.Error
    데이터 로드 실패 시

✅ getRefreshKey()

getRefreshKey() 함수는 초기 Key값이나 데이터 로드 중단 후 재 로드시 이전 Position에서 중단된 Key값을 가져오는 등 load()에 필요한 Key값을 가져오는데 사용되는 함수입니다. 따라서, load()이전에 반드시 선행되는 함수라고 볼 수 있습니다. PagingState 객체를 parameter로 취하고 데이터가 첫 로드 후 새로 고침되거나 무효화 되었을 때 Key를 반환하여 load()로 전달하게 됩니다.

override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {

    return state.anchorPosition?.let { anchorPosition ->
        state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
            ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
    }
}

만약 prevKeynull일 경우 첫 번째 Page를 반환하고 nextKeynull일 경우 마지막 Page를 반환합니다. 가져올 데이터가 없는 경우 (prevKeynextKey 모두 null일 경우) null을 반환하게 됩니다. 여기서 anchorPosition은 가장 최근에 액세스한 index입니다.

아래는 load() 함수가 각 Key를 수신하고 후속으로 로드할 Key를 제공받는 방법입니다.

⚜️ RemoteMediator

Network에서 데이터 로드와 로드된 데이터를 로컬 데이터베이스에 캐싱하는 역할을 하는 RemoteMediator에 대해 알아봅니다.

PagingSource는 Network 작업을 통해 가져온 데이터에 대한 작업을 정의하지만RemoteMediator를 추가로 사용할 경우 가져온 데이터를 캐싱할 수 있는 기능을 제공합니다. 따라서, RemoteMediator를 사용하게 되면 PagingSource는 캐시된 데이터만을 사용하게 되며 UI에서 처리되는 데이터를 처리하게 됩니다.

RemoteMediator에 구현에 대한 자세한 내용은 다음 포스팅에서 확인할 수 있습니다.

⚜️ References

Android Developers 공식 문서
찰스님 블로그

좋은 웹페이지 즐겨찾기