RxJava 안드로이드 프로젝트에 적용하기 02 (with Paging 3)
Paging3 라이브러리에 RxJava를 적용하는 과정에 대한 기록.
Paging3의 자세한 사용법에 대한 포스팅은 아니고, Sunflower 내에 Unsplash Api를 사용해서 사진을 가져올 때 사용한 Paging3에 대해 Flow -> RxJava로 변환하는 과정을 기록한다.
필요한 의존성들을 다음과 같이 추가한다.
// Paging3
implementation "androidx.paging:paging-runtime:$paging_version"
implementation "androidx.paging:paging-rxjava2:$paging_version" // Paging for RxJava
// Retrofit2
implementation "com.squareup.retrofit2:retrofit:$room_version"
implementation "com.squareup.retrofit2:converter-gson:$room_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$room_version" // Retrofit2 for RxJava
Unsplash Service를 작성한다. 지난번 포스팅에서 말한것처럼 일반적으로 서버 api 통신에는 Single 클래스를 사용해서 가져온다.
interface UnsplashService {
@GET("search/photos")
fun searchPhotos(
@Query("query") query: String,
@Query("page") page: Int,
@Query("per_page") perPage: Int,
@Query("client_id") clientId: String = BuildConfig.UNSPLASH_ACCESS_KEY
): Single<UnsplashSearchResponse>
}
Repository를 작성한다. 스크롤 할 때마다 데이터를 지속적으로 방출받을 것이므로 리턴 타입으로 Flowable 클래스를 사용한다.
class UnsplashRepositoryImpl @Inject constructor(
private val service : UnsplashService
): UnsplashRepository {
override fun searchPictures(query: String): Flowable<PagingData<UnsplashPhoto>> {
return Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false),
pagingSourceFactory = { UnsplashPagingSource(service, query) }
).flowable
}
}
PagingSource를 작성한다.
기본적으로 Paging3는 Flow를 기반으로 되어 있고, Rx를 사용하기 위해선 RxPagingSource를 사용해야 한다.
private const val UNSPLASH_STARTING_PAGE_INDEX = 1
class UnsplashPagingSource(
private val service: UnsplashService,
private val query: String
) : RxPagingSource<Int, UnsplashPhoto>() {
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, UnsplashPhoto>> {
val page = params.key ?: UNSPLASH_STARTING_PAGE_INDEX
return service.searchPhotos(query, page, params.loadSize)
.subscribeOn(Schedulers.io())
.map { toLoadResult(it, page) }
.onErrorReturn { LoadResult.Error(it) }
}
private fun toLoadResult(data: UnsplashSearchResponse, page: Int) : LoadResult<Int, UnsplashPhoto> {
return try {
LoadResult.Page(
data = data.results,
prevKey = if (page == UNSPLASH_STARTING_PAGE_INDEX) null else page - 1,
nextKey = if (page == data.totalPages) null else page + 1
)
} catch (exception: Exception) {
LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, UnsplashPhoto>): Int? {
return state.anchorPosition?.let { anchorPosition ->
// This loads starting from previous page, but since PagingConfig.initialLoadSize spans
// multiple pages, the initial load will still load items centered around
// anchorPosition. This also prevents needing to immediately launch prepend due to
// prefetchDistance.
state.closestPageToPosition(anchorPosition)?.prevKey
}
}
}
viewModel에서 repository를 통해 데이터를 호출한다.
지난번 포스팅과 마찬가지로 Disposable 객체에 연결하여 view, viewModel이 파괴될 경우 데이터 수집을 강제로 중단시킨다.
private val _photoList = MutableLiveData<PagingData<UnsplashPhoto>>()
val photoList : LiveData<PagingData<UnsplashPhoto>>
get() = _photoList
private fun searchPictures() {
compositeDisposable.add(
unsplashRepository.searchPictures(query)
.cachedIn(viewModelScope)
.subscribe { _photoList.postValue(it) }
)
}
view에서는 다음과 같이 adpater에 연결하여 사용하였다.
private fun observeData() {
viewModel.photoList.observe(viewLifecycleOwner) {
adapter.submitData(lifecycle, it)
}
}
Flow를 공부하다보면 반대로 Rx로 작성된 코드들이 많아서, 좀 더 이해를 위해 Rx를 공부했지만, 공부할수록 새삼 Flow가 얼마나 편하게 사용할 수 있게 만들어져 있는지 알 수 있다.
애초에 안드로이드에서는 Google에서 Coroutine / Flow를 기반으로 만들고, Rx를 추가로 지원해주는듯한 모습이기도 하고.
Reference: https://medium.com/@fandygotama/paging-3-using-rxjava-3ddc218e4dba
'RxJava' 카테고리의 다른 글
| RxJava 안드로이드 프로젝트에 적용하기 01 (with Sunflower) (0) | 2022.04.05 |
|---|---|
| RxJava 06 - RxJava의 디버깅과 테스트 (0) | 2022.02.16 |
| RxJava 05 - Processor/Subject (0) | 2022.02.16 |
| RxJava 04 - Flowable과 Observable의 연산자 (part.04) (0) | 2022.01.25 |
| RxJava 04 - Flowable과 Observable의 연산자 (part.03) (0) | 2022.01.18 |