Jetpack Paging3 - 3. PagingData 구성하기
⚜️ 사용할 API 결정하기
PagingData를 구성하기 위해 사용할 API를 결정하는 방법에 대해 설명합니다.
PagingData를 구성하기 위해 사용할 API를 결정하는 방법에 대해 설명합니다.
우리는 지금까지 PagingSource
를 통해 데이터를 가져오기 위한 방법을 정의하였습니다. 이를 위해 페이징된 데이터를 로드하기 위해 Key를 정의하였습니다. 지금부터는 Repository Layer
에서 구현된 PagingSource
객체를 통해 반응형 Stream을 구성하기 위해 사용할 방법을 결정합니다.
Codelab에서는 Flow<RepoSearchResult>
를 사용합니다. 단순히 Network 통신에 따른 결과를 RepoSearchResult
sealed class를 통해 필터링해서 UI에 전달합니다. 이 방법은 Paging을 사용하지 않고 Network 통신에 따른 전체 데이터를 가져오는 방식입니다.
효과적으로 Paging을 사용하기 위해 Flow<RepoSearchResult>
를 Flow<PagingData<Repo>>
형태로 변경하게 됩니다. 이렇게 사용할 경우 PagingData
를 발행하는 반응형 Stream형태로 생성되며 각각의 PagingData
인스턴스는 페이징된 데이터(Repo
) snapshot의 container를 나타냅니다.
Paging을 사용할 경우 Network Response의 sealed class를 대체하는 LoadResult
를 사용하여 Response Success/Failure를 모두 모델링하게 되므로 더이상 sealed class가 필요하지 않게 됩니다.
아래 API중 하나를 선택할 수 있습니다. 대표적으로는 Kotlin Flow
를 사용하기도 합니다.
- Kotlin Flow -
Pager.flow
- LiveData -
Pager.liveData
- RxJava Flowable -
Pager.flowable
- RxJava Observable -
Pager.observable
⚜️ Pager
Flow 형태의 반응형 Stream을 구성하기 위해 사용되는 Pager 클래스와 사용되는 parameter에 대해 알아봅니다.
Flow 형태의 반응형 Stream을 구성하기 위해 사용되는 Pager 클래스와 사용되는 parameter에 대해 알아봅니다.
Flow 형태의 반응형 Stream을 구성하기 위해 Pager
를 Flow 형태로 변환하게 됩니다.
Pager
클래스는 다음과 같은 주 생성자를 포함합니다.
public class Pager<Key : Any, Value : Any>
@ExperimentalPagingApi constructor(
config: PagingConfig,
initialKey: Key? = null,
remoteMediator: RemoteMediator<Key, Value>?,
pagingSourceFactory: () -> PagingSource<Key, Value>
) { ... }
✅ PageConfig
PageConfig
클래스는 데이터 로드 시 대기시간, 초기 로드할 데이터의 크기등 PagingSource
에서 데이터를 로드하는 방법에 관한 옵션을 설정하는 클래스입니다.
pageSize (Int)
반드시 정의해야 할 필수 parameter는 각 페이지에서 로드해야 하는 항목의 수를 가리키는 pageSize
입니다.
pageSize
는 UI에 표시되는 항목 수의 몇 배여야 합니다. pageSize
가 작을수록 메모리 사용량이 향상되고 데이터 로드 대기 시간이 줄어들지만 항목이 화면 전체를 차지하지 못하기 때문에 스크롤 시 깜빡이는 현상이 발생하기도 합니다. 반면, pageSize
가 클수록 로드 효율은 좋지만 가져오는 목록이 업데이트 시 대기 시간이 늘어날 수도 있습니다. 따라서 공식 문서에서는 적절한 pageSize
를 고려하도록 제시하고 있습니다. 대량의 항목을 표시해야 할 경우 pageSize = 100
에 가깝게, 화면 대부분을 차지하는 항목의 경우 pageSize = 10 ~ 20
이 적당하다고 말합니다. (애매모호한 설명이지만 비지니스 로직에 따라서 알맞게 정해야 할 것 같습니다.)
Codelab에서는 1 페이지 당 30개의 항목을 가져옵니다.
prefetchDistance (Int)
prefetchDistance
는 현재 로드된 페이지에서 다음 페이지 로딩을 트리거할 시기를 결정하는 parameter입니다. 예를 들어, prefetchDistance = 50
일 경우 이미 로드된 페이지가 50개의 항목까지 디스플레이 되었을때 다음 목록을 미리 로딩하게 됩니다.
enablePlaceHolders (Boolean)
가져올 페이지가 없는 경우 (PagingSource
가 Null
일 경우) placeholder를 표시할 것인지를 결정합니다. enablePlaceHolders = true
로 설정될 경우 PagingSource
에서 아직 로드되지 않은 아이템의 갯수 (Null
의 갯수)에 따라 placeholder를 표시하게 됩니다.
initialLoadSize (Int)
PagingSource
가 초기에 로드할 페이지의 크기를 정의합니다. 일반적으로는 기본 pageSize
보다 크기 때문에 첫번째 페이지에서 로드할 항목은 사용자의 작은 스크롤에도 포함될 정도의 충분히 넓은 범위의 항목을 로드하게 됩니다. 기본적으로, pageSize
의 3배정도를 로드하게 됩니다.
public val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
// pageSize * 3
아래는 Codelab에서 페이지 로딩 시 확인되는 로그입니다. 기본으로 설정된 PageSize
는 30이고 초기에 가져오는 항목은 90개임을 확인할 수 있습니다.
maxSize (Int)
PagingData
객체에서 로드하고 있는 항목의 최대 갯수를 정의합니다.
Paging의 컨셉은 가져올 데이터를 한번에 가져오지 않고 일정 크기의 Chunk 단위로 나누어 가져오는 것입니다. Paging을 사용하여도 사용자가 스크롤하면서 언젠가 모든 항목을 가져오게 될것인데, maxSize
는 이 항목을 화면에 모두 보유하고 있을 것인지를 결정합니다.
maxSize
는 최소 pageSize
+ (2 * prefetchDistance
)여야 합니다. 기본적으로는 Integer의 최대를 가지고 있고 이 경우, 로드한 페이지가 절대 drop되지 않습니다.
public val maxSize: Int = MAX_SIZE_UNBOUNDED
// MAX_SIZE_UNBOUNDED == Int.MAX_VALUE
jumpThreshold (Int)
이 parameter는 사실 정확히 알지 못해서 작성하지 못했습니다. 조금 더 공부하고 업데이트 하도록 하겠습니다 ㅠㅠ
✅ Key
Pager
로 전달된 initialKey의 Type이 지정됩니다.
✅ RemoteMediator
로컬 캐싱을 위해 RemoteMediator 클래스를 구현한 클래스가 지정됩니다.
✅ PagingSource
PagingSource
를 구현한 클래스가 지정됩니다.
⚜️ Pager를 Stream 형태로 변환
UI Layer에서 사용될 Stream 형태의 PagingData로 변환하기 위한 방법을 설명합니다.
UI Layer에서 사용될 Stream 형태의 PagingData로 변환하기 위한 방법을 설명합니다.
Codelab에서는 아래와 같이 구현하고 있습니다.
fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = { GithubPagingSource(service, query) }
).flow
}
companion object {
const val NETWORK_PAGE_SIZE = 30
}
Flow 형태로 변환하기 위해 Pager.flow
를 사용하고 있습니다.
내부 코드를 살펴보겠습니다.
@OptIn(androidx.paging.ExperimentalPagingApi::class)
public val flow: Flow<PagingData<Value>> = PageFetcher(
pagingSourceFactory = if (
pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
) {
pagingSourceFactory::create
} else {
// cannot pass it as is since it is not a suspend function. Hence, we wrap it in {}
// which means we are calling the original factory inside a suspend function
{
pagingSourceFactory()
}
},
initialKey = initialKey,
config = config,
remoteMediator = remoteMediator
).flow
기존에 구현한 PagingSource
클래스가 suspend
한지 확인합니다. suspend
한 경우 생성한 PagingSource
를 그대로 사용하게 되고 그렇지 않을 경우, { }로 매핑하게 됩니다. 이는 suspend
함수 내에서 원래 Factory를 호출하는 것을 의미합니다.
⚜️ ViewModel에서 사용법
반응형 Stream으로 변환한 데이터를 UI에서 사용하기 위해 어떻게 ViewModel에서 처리해야 하는지 설명합니다.
반응형 Stream으로 변환한 데이터를 UI에서 사용하기 위해 어떻게 ViewModel에서 처리해야 하는지 설명합니다.
기존에는 Flow
형태의 데이터를 LiveData<RepoSearchResult>
형태로 노출하게 됩니다. Paging을 사용할 경우 더 이상 Flow
를 LiveData
형태로 변환하지 않아도 됩니다. ViewModel 클래스에는 기존에 데이터를 가져오고 검색에 따른 데이터를 캐싱하기 위해 LiveData<>
형태로 저장하던 것과 같은 역할을 하는 Flow<PagingData<Repo>>
멤버 변수가 포함됩니다.
Flow<PagingData>
에는 CoroutineScope
에서 항목을 캐시할 수 있는 함수인 cachedIn()
가 있습니다. 즉, Flow에서 map 또는 filter와 같은 작업을 실행할 경우 작업 실행 후 cachedIn()
함수를 호출하여 작업을 다시 트리거하지 않도록 해야합니다.
class SearchRepositoriesViewModel(
private val repository: GithubRepository
) : ViewModel() {
private var currentQueryValue: String? = null
private var currentSearchResult: Flow<PagingData<Repo>>? = null
fun searchRepo(queryString: String) : Flow<PagingData<Repo>> {
val lastResult = currentSearchResult
if (queryString == currentQueryValue && lastResult != null) {
return lastResult
}
currentQueryValue = queryString
val newResult: Flow<PagingData<Repo>> = repository.getSearchResultStream(queryString)
.cachedIn(viewModelScope)
currentSearchResult = newResult
return newResult
}
}
기존 데이터를 currentSearchResult
로 가지고 있고 검색에 따라 변경되는 데이터를 currentSearchResult
에 저장하고 newResult
로 반환하는 형태입니다. 같은 작업을 트리거하지 않도록 cachedIn()
함수를 사용하는 것을 볼 수 있습니다.
⚜️ References
Android Developers 공식 문서
찰스님 블로그
Author And Source
이 문제에 관하여(Jetpack Paging3 - 3. PagingData 구성하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jslee-dev/Jetpack-Paging3-3.-PagingData-구성하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)