Seoplee
개발의 섭리, Seoplee의 개발
Seoplee
  • 분류 전체보기 (54)
    • Android (26)
      • Architecture (12)
      • Compose (0)
      • Tips (11)
      • 트러블슈팅 (3)
    • IOS (1)
      • Tips (1)
    • Kotlin (1)
    • Coroutine (3)
      • Flow (3)
    • RxJava (12)
    • CI&CD (1)
    • WEB (8)
    • Network (1)
    • ETC (1)
    • (임시) (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Seoplee

개발의 섭리, Seoplee의 개발

RxJava

RxJava 안드로이드 프로젝트에 적용하기 02 (with Paging 3)

2022. 4. 5. 17:59

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
    'RxJava' 카테고리의 다른 글
    • RxJava 안드로이드 프로젝트에 적용하기 01 (with Sunflower)
    • RxJava 06 - RxJava의 디버깅과 테스트
    • RxJava 05 - Processor/Subject
    • RxJava 04 - Flowable과 Observable의 연산자 (part.04)
    Seoplee
    Seoplee
    개발공부를 하며 기록할만한 것들을 정리해놓은 블로그입니다.

    티스토리툴바