(번역)MVP로 안드로이드 페이지네이션하기

토마스·2023년 5월 14일
0

원문: https://efebu.medium.com/android-pagination-with-mvp-79c38bc1568e

이 글은 위 글을 번역/요약한 글로, 모든 저작권은 원문의 저자인 efe budak에게 있습니다.


안드로이드 앱에서 페이지네이션은 흔히 사용됩니다. MVP 패턴에서 페이지네이션을 구현한 간단한 예시를 보여드리겠습니다.

Page State

"현재 페이지"를 추적하려면 프레젠터에 currentPage 변수가 필요합니다. 어떤 호출이 만들어질지, 혹은 호출이 필요한지 이해하기 위해서는 이 작은 Int형 변수를 가지는 것이 중요합니다.

뷰는 몇 페이지를 보여주고 있는지 알 필요가 없고 알아서도 안됩니다. 그러나 뷰는 "새로고침 버튼이 클릭되었다." 혹은 "사용자가 페이지의 맨아래에 도달했다"와 같은 사용자의 활동에 대해 알려야 합니다. 이러한 행동을 위한 프레젠터의 콜백 메서드를 가지고 있을 것입니다.

fun onLoadNextPage() {
	currentPage++
    view.setRefreshing(true)
    getPage()
}

fun onRefresh() {
	currentPage = 0
    view.setRefreshing(true)
    productRepository.refresh()
    getPage()
}

onRefresh 메서드는 매우 간단합니다. 당신이 앱을 만들었다면 아마 결과를 새로고침하기 위해 클릭하거나 스와이프 시 이 메서드를 호출하는 버튼을 가지고 있을 것입니다.

onLoadNextPage는 약간 까다롭습니다. 유저가 현재 페이지 아이템들의 마지막에 가까이 있는지 알려면 당신은 리사이클러 뷰에 리스너를 추가해야 합니다.
아래는 예시 코드입니다.

binding.recyclerViewMainProduct.addOnScrollListener(object :
    RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val visibleItemCount = recyclerView.layoutManager?.childCount
        val totalItemCount = recyclerView.layoutManager?.itemCount
        val firstVisibleItemPosition =
            (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()

        if (!swipeRefreshLayout.isRefreshing() && !endOfList) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount - Constants.PAGINATION_MARGIN
                && firstVisibleItemPosition >= 0
                && totalItemCount >= Constants.PAGE_SIZE
            ) {
                presenter.onLoadNextPage()
            }
        }
    }
})

여기로 가면 이 코드에 대한 자세한 정보를 알 수 있습니다.

Constants.PAGINATION_MARGIN은 당신의 호출의 마진입니다. 예를 들어, 당신의 페이지에 20개의 아이템을 가지고 있고, 15번째 아이템이 보일 때 다음 페이지를 로드하길 원한다면 당신은 마진을 5로 설정해야 합니다.

endOfList는 로드할 페이지가 더 없을 경우의 플래그입니다. 당신은 이 플래그의 상태를 저장해야 합니다.

Loading Page

우리는 우리의 리스트를 채울 결과가 필요합니다. 그래서 여기 프레젠터의 getPage 메서드가 있습니다.

private void getPage() {
    mCompositeDisposable.add(mRepository.getPage(mCurrentPage)
            .observeOn(mSchedulerProvider.ui())
            .subscribeOn(mSchedulerProvider.io())
            .subscribeWith(new DisposableSingleObserver<Products>(){

                @Override
                public void onSuccess( @io.reactivex.annotations.NonNull Products products) {
                    mView.setRefreshing(false);
                    mView.refreshPageItems(products.getProducts());
                    if (products.getNumberPages() <= products.getCurrentPage()) {
                        mView.stopPagination();
                    }
                    mCurrentPage = products.getCurrentPage();
                }

                @Override
                public void onError(@io.reactivex.annotations.NonNull Throwable e) {
                    mView.setRefreshing(false);
                    mView.showError();
                    if (mCurrentPage > 0) {
                        mCurrentPage--;
                    }
                }
            }));
}

(이 코드는 어려워서 그대로 복붙 했습니다.)
이 코드는 RxJava2를 사용했습니다. 이 코드는 기본적으로 요청과 콜백이기 때문에 그렇게 복잡하지 않습니다. 만약 응답이 성공적이면 "페이지 아이템이 새로고침"됩니다.

여기서 상품 리스트(products)는 그것의 최종 상태입니다. 그래서 만약 두 번째 페이지 요청이고 각 요청이 20개의 항목을 가지고 있다면, products.getProducts()는 40개의 항목을 포함합니다.

또는, 프레젠터에 당신의 항목들을 유지할 수 있지만 CacheDataSource를 사용한 저장소에 그것들을 유지하는 것이 더 좋습니다.

if (products.getNumberPages() <= products.getCurrentPage()) {
    mView.stopPagination();
}

뷰의 endOfList 변수는 현재 페이지가 전체 페이지보다 크거나 같은지를 나타내는 변수입니다.

if (mCurrentPage > 0) {
    mCurrentPage--;
}

만약 현재 페이지를 로드할 수 없다면 현재 페이지를 감소시킵니다.

Remember CurrentPage

당신이 만든 액티비티는 안드로이드 프레임워크에 의해 언제든지 파괴될 수 있습니다. 따라서 그 상황에 대처해야 합니다. 해결 방법은 새롭지도 않고 어렵지도 않습니다. 바로 onSaveInstanceState()을 사용하는 것입니다.

프레젠터가 현재 페이지를 위한 세터/게터 메소드를 가지고 있고 당신의 액티비티가 그것을 호출하면 됩니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_home)
    
    presenter = Presenter(
        fragment,
        SchedulerProvider.getInstance(),
        Repository.getInstance(
            LocalDataSource.getInstance(),
            RemoteDataSource.getInstance()
        )
    )
    if (savedInstanceState != null)) {
        presenter.restoreCurrentPage(savedInstanceState?.getInt(BUNDEL_CURRENT_PAGE))
    }
}

override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
    outState.putInt(BUNDLE_CURRENT_PAGE, presenter.getCurrentPage())
    super.onSaveInstanceState(outState, outPersistentState)
}

단지 프레젠터를 생성 후 프레젠터의 회복 메서드(restoreCurrentPage())를 호출하면 됩니다.


번역이 잘못되었거나 부족한 점이 있다면 댓글로 알려주시면 감사하겠습니다.

profile
안드로이드 개발자 지망생

0개의 댓글