Paging3
Android
에서 지원해주는 페이징 라이브러리로, PagingConfig
와 PagingSource
혹은 RemoteMediator
의 정보를 토대로 Pager
를 통해 PagingData
를 생성한 후, 해당 인스턴스를 PagingDataAdapter
를 사용하여 UI
에 반영하는 식으로 동작한다.
Pager / PagingConfig
PagingConfig
는 로드할 페이지를 설정하는 클래스이다.
initialLoadSize
는 최초 로드할 페이지의 크기를 설정하는 옵션으로, 기본값은pageSize
와DEFAULT_INITIAL_PAGE_MULTIPLIER
를 곱한 값을 사용한다.pageSize
는 로드할 페이지의 크기를 설정하는 옵션이다prefetchDistance
는 현재 로드된 페이지의 최상단 혹은 최하단에 도달하기 얼마 전에 다음 페이지를 로드할 것인지에 대한 옵션이다.enablePlaceholders
는 로드할 페이지가 없을 경우placeHolder
를 표시할 것인지에 대한 옵션으로,true
를 설정하면 아직 로드 되지 않은 아이템의 갯수(null
갯수)에 따라서placeHolder
가 표시된다.
PagingConfig
와 PagingSource
혹은 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
은 가장 최근에 접근한 인덱스로, 주변 데이터를 다시 로드할 때 사용되며prevKey
가null
이면 첫 페이지,nextKey
가null
이면 마지막 페이지, 둘 다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()
함수의 반환형인MediatorResult
에endOfPaginationReached
인자에 넘겨주어 데이터 로드를 끝낼지를 판단한다.