Paging3

Paging 라이브러리

Paging3

Paging3


Android 에서 지원해주는 페이징 라이브러리로, PagingConfigPagingSource 혹은 RemoteMediator 의 정보를 토대로 Pager 를 통해 PagingData 를 생성한 후, 해당 인스턴스를 PagingDataAdapter 를 사용하여 UI 에 반영하는 식으로 동작한다.



Pager / PagingConfig


PagingConfig 는 로드할 페이지를 설정하는 클래스이다.

  • initialLoadSize 는 최초 로드할 페이지의 크기를 설정하는 옵션으로, 기본값은 pageSizeDEFAULT_INITIAL_PAGE_MULTIPLIER 를 곱한 값을 사용한다.
  • pageSize 는 로드할 페이지의 크기를 설정하는 옵션이다
  • prefetchDistance 는 현재 로드된 페이지의 최상단 혹은 최하단에 도달하기 얼마 전에 다음 페이지를 로드할 것인지에 대한 옵션이다.
  • enablePlaceholders 는 로드할 페이지가 없을 경우 placeHolder 를 표시할 것인지에 대한 옵션으로, true 를 설정하면 아직 로드 되지 않은 아이템의 갯수(null 갯수)에 따라서 placeHolder 가 표시된다.

PagingConfigPagingSource 혹은 RemoteMediator 의 정보를 토대로 PagingData 를 생성한 뒤 스트림화 해주는 클래스이다.

// Use PagingSource
val pagingDataUsePagingSource: Flow<PagingData<Data>> = Pager(
    config = PagingConfig(initialLoadSize = 50, pageSize = 50)
) {
    PagingDataSource()
}.flow.cachedIn(viewModelScope)

// Use RemoteMediator
val pagingDataUseRemoteMediator: Flow<PagingData<Data>> = Pager(
    config = PagingConfig(initialLoadSize = 50, pageSize = 50),
    remoteMediator = PagingRemoteMediator()
) {
    PagingDataSource()
}.flow



PagingSource


DataSource 를 정의하기 위한 클래스로, Key 타입과 반환할 데이터 타입을 제네릭으로 받는다.

class PagingDataSource(
    private val api: Api
): PagingSource<Int, Data>() {

    companion object {
        private const val START_PAGE_INDEX = 1
    }

    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, Data> {
        val page = params.key ?: START_PAGE_INDEX

        return try {
            val response = api.fetchImage(page)
            
            LoadResult.Page(
                data = response,
                prevKey = if (page == START_PAGE_INDEX) {
                    null 
                } else { 
                    page.minus(1)
                },
                nextKey = if (response.hasNextPage()) {
                    page.plus(1) 
                } else { 
                    null
                }
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(
        state: PagingState<Int, Data>
    ): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }
}
  • load() 는 데이터를 가져오는 로직을 구현하기 위한 함수이다.
  • LoadParams 에는 로드 작업과 관련된 정보(로드할 페이지의 Key, 로드할 페이지의 크기)가 저장된다.
  • getRefreshKey() 는 데이터 업데이트 등 현재 페이지 목록을 대체할 새로운 데이터를 가져오는 로직을 구현하기 위한 함수이다.
  • PagingState 는 현재 로드된 페이지와 마지막으로 접근한 페이지의 스냅샷 상태를 가지고 있다.
  • anchorPosition 은 가장 최근에 접근한 인덱스로, 주변 데이터를 다시 로드할 때 사용되며 prevKeynull 이면 첫 페이지, nextKeynull 이면 마지막 페이지, 둘 다 null 이면 초기화된 페이지로 null 을 반환한다.


RemoteMediator


DB 캐싱에 관련된 역할을 수행하는 클래스로, 서버에서 받은 데이터를 로드하고, 로드된 데이터를 DB 에 저장하는 역할을 수행하며, PagingSource 는 캐시된 데이터만을 사용하여 UI 에 사용할 데이터를 처리한다.

class PagingRemoteMediator: RemoteMediator<Int, Data>(){

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, Data>
    ): MediatorResult {
        MediatorResult.Success(endOfPaginationReached = true)
    }

    override suspend fun initialize(): InitializeAction {
        return InitializeAction.SKIP_INITIAL_REFRESH
    }
}
  • Key 값을 내부에서 제공하지 않기 때문에 Key 가 필요한 경우에는 DB 에서 직접 생성하고 관리 해야한다.
  • LoadType 을 활용하여 데이터를 추가로 로드할 여부에 대한 로직을 구현하고, 결과를 load() 함수의 반환형인 MediatorResultendOfPaginationReached 인자에 넘겨주어 데이터 로드를 끝낼지를 판단한다.
essential