최근에 공부하면서 배운 Paging에 대해 얘기를 정리하고자 합니다.
요구사항을 해결하기 위해선, pageKey를 header에 붙여야 했습니다.
일단 첫 번째로 해야 할 것은 PageKey를 받아 오는 일이었습니다.
가져올 곳은 PagingSource에서만 가능합니다.
문제는 도메인 모델링을 어떻게 정의할까 고민이었습니다.
도메인 레이어의 Entity에 Header
를 만드는 게 맞을까요?
//.. 이 코드가 과연 도메인 레이어에 있어도 되는 걸까?
sealed class Document(){
data class Header
data class Image
...
비즈니스로직이 들어가야 하는 레이어에 Header
라니.. 있으면 안 되는 느낌이었습니다.
그래서 UIModel에서 분리를 하자고 생각을 했습니다.
UiModel을 만들려면 ViewModel쪽에서 mapper를 통해 계층간 이동을 하는게 가장 이상적인 코드입니다.
하지만 Paging
라이브러리를 사용하게 되면, PagingData<T>
형태로 감싸지게 되기 때문에 직접적으로 Element를 add하기가 불가능합니다.
결국 해결하려면 도메인 레이어의 Entity에 Header에 관련된 클래스나 구분하는 변수가 추가되어야 했습니다.
구글 애들도 이런것을 알았는지 새로운 함수를 추가해주었습니다.
PagingData<T>.insertSeparators()
는 요소 하나하나를 확인하며 중간에 원소를 끼워넣을 수 있습니다.
val pagingFlow = _queryFlow.flatMapLatest {
getImageForPagingUseCase(it)
}.map { pagingData ->
pagingData.map {
PagingModel.RepoItem(it.document, it.key)
}
}.map { pagingModel ->
//reciver로 before과 after를 받을수 있다.
pagingModel.insertSeparators { before, after ->
if (after == null) {
return@insertSeparators null
}
if (before == null) {
return@insertSeparators PagingModel.HeaderItem(after.pageNum)
}
if (before.pageNum != after.pageNum) {
return@insertSeparators PagingModel.HeaderItem(after.pageNum)
} else {
return@insertSeparators null
}
}
}.cachedIn(viewModelScope)
insertSeparators
는 후행람다로 들어오는 before
(이전 원소)와 after
를 이용해서 원소를 끼어넣을 수 있기 때문에 Header를 넣으려면 이전원소와 다른 원소에서 구분할 수 있는 프로퍼티가 있어야 합니다.
우리는 아까 전에 PageKey
를 pagingSource에서 넘겼기 때문에 쉽게 구현이 가능합니다.
if (before.pageNum != after.pageNum) {
return@insertSeparators PagingModel.HeaderItem(after.pageNum)
} else {
return@insertSeparators null
}
몇가지의 방법이 있습니다만, 거의 아래의 두가지 방법이 많이 쓰입니다.
RemoteMediator
를 넣게되면 Room에서 데이터 스트림을 받게되고, 변경사항이 있을때(업데이트라던지 삭제라던지) 자동으로 방출을 하게되어 collect만해도 자동으로 변경사항을 구독할수 있습니다.
다만 단점은 remoteMediator를 사용해야하는 점과 모듈분리시에 domain Entity에 isClicked가 들어가도 되는가에 대한 문제가 생깁니다.
pagingFlow는 map을 통해 내부를 변경할 수 있습니다.
combine()
연산자를 이용하여 서로의 flow
를 합친뒤, local에 담아둔 flow
를 이용하여 수정합니다.
Room을 사용하지않는다면 아래의 방법을 써봄직 합니다.
private val _localDataList = MutableStateFlow(listOf<UpdateData>())
// Observe the Paging 3 Flow and combine it with the local Flow
val combinedDataList = paging3Flow.combine(_localDataList.) { paging, local ->
// Find and update or remove the desired item in the list
paging.map {
local.find { localItem -> localItem.item.id == it.id }?.let { localItem ->
when (localItem.updateType) {
UpdateType.UPDATE -> localItem.item
UpdateType.REMOVE -> null
}
} ?: it
}.filter { it!=null }
}
새로운 Android Paging v3를 사용한 CRUD 작업
코드를 짜고보니 나름 재밌게 만든것 같습니다.
모두들 재밌는 코딩하셨으면 좋겠습니다.