Paging 라이브러리란?
페이징을 쉽게 구현하기 위해 안드로이드에서 제공하는 라이브러리이다.
최근 3.0 Alpha 버전이 릴리즈 되었단 걸 알게 되었다.
Paging2와 변한 점
- DataSource 관련 코드가 PagingSource 하나로 통합되었다.
- Header, Footer, separator를 넣을 수 있는 코드 (insertHeaderItem, insertFooterItem, insertSeparators)가 추가되었다.
- 로딩 처리를 쉽게 할 수 있다.
- 데이터 refresh 에 대한 처리가 추가되었다.
- 로딩 시 발생하는 에러 처리를 쉽게 할 수 있다.
- 더 이상 Config 생성 방식이 빌더 패턴이 아니다.
- 데이터의 캐싱이 가능하다.
위에 7가지 정도가 바뀌었다.
기존 Paging 보단 코드가 훨씬 깔끔하게 나오는 걸 느꼈다.
Paging3 Library 시작하기
이제 위와 같은 간단한 샘플을 만들어보려 한다.
라이브러리 추가하기
dependencies {
def paging_version = "3.0.0-alpha01"
implementation "androidx.paging:paging-runtime:$paging_version"
// testImplementation "androidx.paging:paging-common:$paging_version" // test 용
// implementation "androidx.paging:paging-rxjava2:$paging_version" // rxJava2 support
// implementation "androidx.paging:paging-guava:$paging_version" // guava support
}
Paging Source 만들기
class PagingRepository {
fun getPagingData(page: Int): Pair<List<String>, Int?> {
return if (page <= 30) Pair(listOf("A $page", "B $page", "C $page"), page + 1)
else Pair(listOf(), null)
}
}
우선 테스트용 Repository를 만들었다.
데이터는 30 Page까지만 존재하고 각 페이지에는 [ A, B, C ] 3개의 문자열만 가지고 있다.
class DooolPagingSource(
private val repository: PagingRepository
) : PagingSource<Int, String>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
return try {
val nextPage = params.key ?: 1
val response = repository.getPagingData(nextPage)
LoadResult.Page(
data = response.first,
prevKey = null,
nextKey = response.second
)
} catch (e: Exception) {
LoadResult.Error(Throwable("Paging Error"))
}
}
}
추상 클래스인 PagingSource<Key : Any, Value : Any>
를 상속받아서 DooolPagingSource를 만들었다.
인자로 넘어오는 params의 key 값이 페이지 정보이다. 추가로 loadSize ( 한 번에 불러올 사이즈 ) 도 포함하고 있다.
return 값인 LoadResult는 Page, Error 두 가지가 있다.
LoadResult.Page
는 정상적인 흐름일 때 사용하면 된다.
prevKey, nextKey를 둘 다 null로 주면 페이징은 더 이상 데이터를 불러오지 않는다.
LoadResult.Error
는 Exception 발생이나 데이터에 문제가 있을 경우 사용하면 된다.
return 할 시에는 자동 리프레시 같은 건 없다. 그냥 끝이다.
데이터 모델 정의하기
enum class DataType {
HEADER, ITEM, SEPARATOR
}
sealed class DataModel(val type: DataType) {
data class Item(val title: String) : DataModel(DataType.ITEM)
data class Header(val title: String) : DataModel(DataType.HEADER)
object Separator : DataModel(DataType.SEPARATOR)
}
Adapter에서 사용할 데이터를 Item, Header, Separator 세 가지로 정의하였다.
PagingData 만들기
class PagingViewModel : ViewModel() {
...
val flow = Pager(PagingConfig(pageSize = 10)) { // config 설정
DooolPagingSource(PagingRepository()) // pagingSource 연결
}.flow.map {
it.map<DataModel> { DataModel.Item(it) }
.insertHeaderItem(DataModel.Header("HEADER"))
.insertFooterItem(DataModel.Header("FOOTER"))
.insertSeparators { before, after ->
when{
before is DataModel.Item && after is DataModel.Item ->{
if (before.title.startsWith("C") && after.title.startsWith("A")) DataModel.Separator
else null
}
else -> null
}
}
}.cachedIn(viewModelScope) // 캐싱
...
}
PagingSource에서 던져주는 건 그냥 아이템뿐이기에 Pager
를 통해서 PagingData로 변환해줘야 한다.
위에 소스에는 없지만 Pager 생성 시에 초기 키값을 지정해 줄 수 도 있다.
Pager 생성 후 flow 변수 ( 왜 이름을 이렇게... )를 통해서 PagingData를 가져올 수 있다.
PagingData는 insertHeaderItem
, insertFooterItem
, insertSeparators
함수를 통해서
헤더, 푸터, 사이사이 아이템을 넣는 게 가능하다.
위에 코드에선 헤더와 푸터에 Header 아이템을 넣고, 페이지와 페이지 사이에 Separator 아이템을 넣었다.
( 페이지의 시작에 A , 끝에 C 문자열이 온다. )
insertSeparators의 before 또는 after 값에는 앞에서 실행한 insert코드의 아이템이 올 수 있으니 주의해야 한다.
캐싱은 cachedIn(viewModelScope)
코드를 사용하면 가능하다. ( cachedIn 은 Flow 타입에서 지원하는 함수이다. )
액티비티에서 한번 불러오면 프래그먼트들이 바로 아이템을 사용하는 것이 가능하다.
PagingDataAdapter 구현하기
class PagingAdapter(diffCallback: DiffUtil.ItemCallback<DataModel>) :
PagingDataAdapter<DataModel, RecyclerView.ViewHolder>(diffCallback) {
...
}
PagingDataAdapter
는 기존 RecyclerView.Adapter의 구현과 동일하다.
차이점은 diffUtil을 구현해줘야 하는 것뿐이다. ( paging2와 동일하다. )
데이터 연결하기
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<PagingViewModel>()
...
override fun onCreate(savedInstanceState: Bundle?) {
...
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter
lifecycleScope.launch {
viewModel.flow.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
}
}
submitData
와 flow
의 함수가 전부 suspend 함수라서 코루틴을 통하여 호출하여야 한다.
데이터 다시 불러오기
adapter.refresh()
adapter.retry()
refresh
는 언제든지 호출하여 처음부터 다시 불러오는 것이 가능하다.
retry
는 일반적인 상황에선 아무런 동작도 하지 않지만, PagingSource에서 LoadResult.Error
를 리턴한 경우에는 에러가 발생한 해당 페이지부터 다시 불러오기 시작한다.
'안드로이드' 카테고리의 다른 글
[And] navigation #1 - 기본 사용법 (1) | 2020.06.18 |
---|---|
안드로이드 paging 3.0 #2 - LoadState (0) | 2020.06.16 |
Dagger - Hilt 간보기 (0) | 2020.06.11 |
Android 에서 미리보기 ( Open Graph ) 만들기 (1) | 2020.03.03 |
Android 에서 Firebase Test Lab 로그인 하기 (0) | 2020.03.03 |