본문 바로가기
안드로이드

[Android] Retrofit2로 recyclerview 페이징 처리하기

by Banlim 2020. 12. 10.

[나중에 참고하기 위해 기록]

 

POST로 요청 시, Request Body 부분을 다음과 같이 한다고 가정한다.

{
    "type": 0
    "startidx": 1
    "endidx": 40
}

 

응답으로 예를들어 다음과 같이 온다고 가정한다.

{
    "resultcode": 0,
    "message": "success",
    "response": [
    {
    	"idx": 1,
        "title": "hello",
        "date": "20201201"
    },
    {
    	"idx": 2,
        "title": "world",
        "date": "20201202"
    },
    {
    	"idx": 3,
        "title": "I'm json",
        "date": "20201203"
    },
    {
    	"idx": 4,
        "title": "retrofit2",
        "date": "20201204"
    },
    {
    	"idx": 5,
        "title": "rxjava2",
        "date": "20201205"
    },
    	. . .
    {
    	"idx": n,
        "title": "final",
        "date": "20201230"
    }
    ]    
}        

 

우선, 이 형태의 json을 받기 위한 클래스를 다음과 같이 작성한다.

 

LoadMoreDataList.java

public class LoadMoreDataList{

    @SerializedName("resultcode")
    @Expose
    private int resultcode;
    
    @SerializedName("message")
    @Expose
    private int message;
    
    @SerializedName("response")
    @Expose
    private List<LoadMoreDataListItem> items = null;
    
    public List<LoadMoreDataListItem> getItems() { return items; }
    
    public class LoadMoreDataListItem {
        
        @SerializedName("idx")
        @Expose
        private int idx;
        
        @SerializedName("title")
        @Expose
        private String title;
        
        @SerializedName("date")
        @Expose
        private String date;
        
        public int getIdx() { return idx; }
        public String getTitle() { return title; }
        public String getDate() { return date; }
        
        public void setIdx(int idx) { this.idx = idx; }
        public void setTitle(String title) { this.title = title; }
        public void setDate(String date) { this.date = date; }
        
    }
}

 

다음으로, 화면 하단에 보여줄 로딩 레이아웃을 xml로 아래 코드와 같이 작성한다.

 

loadmore_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center">
    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:id="@+id/loadmore"
        android:layout_marginVertical="20dp"
        android:layout_gravity="center"
        android:indeterminate="true"
        android:indeterminateDrawable="@drawable/loading"/>
</LinearLayout>

 

다음으로 RecyclerView가 나타나는 Activity이며, RecyclerView 아이템들을 스크롤 시 불러온 모든 데이터가 다 로드되어 화면 아래에 도착하면 다음으로 로드해야할 데이터를 불러오는 곳이다. 해당 기능을 LoadMoreActivity.java에 구현했다. 

 

mPageCnt는 아이템이 로드될 때 몇 개의 아이템을 불러올 지 선언한 변수이다.

mLoadItemCnt는 현재 불러온 아이템의 개수를 의미한다.

isLoading은 현재 로딩중인지 체크하는 변수이다.

isLastLoading은 더이상 불러올 데이터가 없을 경우 true로 바꾸는 변수이다.

 

LoadMoreActivity.java

private RecyclerView recyclerView;
private RecyclerAdapter adapter;
private int type = 0;

private final int mPageCnt = 40;
private int mLoadItemCnt = 40;
private boolean isLoading = false;
private boolean isLastLoading = false;

 

recyclerview에 scrollListener를 달아 스크롤 이벤트를 다루는 부분에 다음 데이터를 불러올 지 결정한다.

onScrolled에서 현재 로딩중인지, 혹시 더 이상 불러올 게 없는지 먼저 체크한다.

현재 로딩중을 체크하는 이유는 간혹 두번 데이터가 로드되어 같은 데이터가 중복으로 보여지는 것을 막기 위함이다.

이렇게 체크한 후, 현재 마지막 아이템에 도달했다면, 이제 데이터를 로드할 것이기 때문에 isLoading을 true로 바꾼 후, onLoadMore() 메소드를 호출하여 데이터를 불러온다.

final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
    
    . . .
    
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int lastItem = linearLayoutManager.findLastVisibleItemPosition();
        
        if(!isLoading & !isLastLoading){
            if(linearLayoutManager != null && lastItem == adapter.getItemCount() - 1){
                isLoading = true;
                onLoadMore(type, adapter.getItemCount());
            }
        }
    }
});    

 

 

onLoadMore() 함수에서 adapter에 있는 item을 null 변수로 하나 더 추가하여 로딩 뷰가 보여질 공간을 만든다.

그리고 adapter에게 아이템이 삽입되었다는 것을 알려준 후, startRxLoadMore() 함수를 호출한다.

 

private void onLoadMore(int type, int startIdx){
    adapter.addItem(null);
    adapter.notifyItemInserted(adapter.getItemCount() - 1);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            startRxLoadMore(type, startIdx + 1, startIdx + mPageCnt);
        }, 1000);
}

 

 

startRxLoadMore() 함수에서 idx를 기준으로 Retrofit2를 활용하여 데이터를 가져온다.

데이터를 가져오면, 로딩 뷰를 띄웠던 아이템을 지우고 새로 불러온 데이터 리스트를 addAll 함수로 넣는다.

데이터 리스트를 삽입한 후, mLoadItemCnt 값을 증가시킨 후, isLoading을 false로 변경하여 로딩이 종료됨을 표시한다.

 

private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
private LoadMoreDataList morelist;

private void startRxLoadMore(int type, int startIdx, int endIdx){
    final String strName = "loadmore_test";
    
    LoadMoreBody body = new LoadMoreBody(type, startIdx, endIdx);
    RestfulServiceApi service = RestfulAdapter.getInstance().getServiceApi();
    Observable<LoadMoreDataList> observable = service.getloadmoredatalist(strName, body);
    
    mCompositeDisposable.add(observable.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeWith(new DisposableObserver<LoadMoreDataList>(){
                    
                    	@Override
                        public void onNext(LoadMoreDataList contributors){
                            morelist = new LoadMoreDataList();
                            morelist = contributors;
                        }
                        
                        @Override
                        public void onError(Throwable e){
                            Log.d(TAG, "onError: " + e.toString());
                        }
                        
                        @Override
                        public void onComplete(){
                            adapter.removeItem(adapter.getItemCount()-1);
                            int scrollPosition = adapter.getItemCount();
                            adapter.notifyItemRemoved(scrollPosition);
                            
                            if(morelist.getItems().size() > 0) {
                                adapter.addAll(morelist);
                                loadChk = true;
                                mLoadItemCnt = scrollPosition;
                                mLoadItemCnt += mPageCnt;
                                isLastLoading = false;
                            } else 
                                isLastLoading = true;
                                
                            isLoading = false;    
                        }
                    })    
    )
}

 

 

LoadMoreBody.java

public class LoadMoreBody {
    private int type, startIdx, endIdx;
    
    public LoadMoreBody(int type, int startIdx, int endIdx){
        this.type = type;
        this.startIdx = startIdx;
        this.endIdx = endIdx;
    }
}

 

 

RecyclerAdapter에서는 데이터가 로드된 아이템을 보여줄 view_type과 로딩 뷰를 보여줄 view_type을 아래와 같이 정의한다. 이를 쓰기 위해서는 getItemViewType(int position) 함수를 Override하여 재정의 해야한다.

 

RecyclerAdapter.java

private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;

 

public void addAll(LoadMoreDataList items){
    this.items.getItems().addAll(items.getItems());
    notifyDataSetChanged();
}

public void removeItem(int itemIdx){
    this.items.getItems().remove(itemIdx);
}

public void addItem(LoadMoreDataListItem item) {
    this.items.getItems().add(item);
}

@Override
public int getItemCount(){
    return this.items != null ? this.items.getItems().size() : 0;
}

@Override
public int getItemViewType(int position){
    return this.items.getItems().get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
}

@Override
public long getItemId(int position){
    return this.items.getItems().get(position) != null ? this.items.getItems().get(position).getIdx() : 0;
}

private void showLoadingView(LoadingViewHolder holder){
    holder.mProgressBar.setVisibility(View.VISIBLE);
}

 

onCreateViewHolder() 함수에서 viewType에 따라 아이템들을 보여줄 경우와 로딩뷰를 보여줄 경우를 두 가지로 나누어 아래와 같이 코드를 작성한다. 이후 onBindViewHolder() 함수를 통해 데이터를 뷰에 바인딩할 때 VIEW_TYPE_ITEM일 경우, 데이터를 바인딩하고, VIEW_TYPE_LOADING일 경우 로딩뷰를 보여주도록 구현한다.

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    if(viewType == VIEW_TYPE_ITEM){
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_item, parent, false);
        return new ViewHolder(view);
    } else {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.loadmore_layout, parent, false);
        return new ViewHolder(view);
    }
}

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position){
    
    switch(getItemViewType(position)){
        case VIEW_TYPE_ITEM:
            ViewHolder viewHolder = (ViweHolder) holder;
            bindItem(viewHolder, position);
            break;
        case VIEW_TYPE_LOADING:
            LoadingViewHolder loadingViewHolder = (LoadingViweHolder) holder;
            loadingViewHolder.mProgressBar.setIndeterminate(true);
            showLoadingView(loadingViewHolder, position);
            break;
    }
}

 

   private class LoadingViewHolder extends ViewHolder {
        ProgressBar mProgressBar;

        public LoadingViewHolder(@NonNull View itemView) {
            super(itemView);
            mProgressBar = itemView.findViewById(R.id.loadmore);
        }
    }

 

 

+)참고한 사이트

   -> RecyclerView Loading Layout

 

isntyet.tistory.com/114

 

(android) RecyclerView bottom endless refresh[리스트 페이징)

기존 프로젝트에서 RecyclerView를 통해 내용을 쭉 불러오고 있었는데 실험삼아 검색기준을 바꾸어 1000개정도의 아이템을 한번에 불러왔더니 역시나 로딩이 오래걸리면서 앱이 버벅거렸다. 그래

isntyet.tistory.com

class-programming.tistory.com/34

 

android infinite/endless scroll

infinite/endless scroll 이란? 페이지 이동없이 스크롤을 내리면 다음 페이지의 내용을 불러오는 것을 의미한다. 즉, 무한스크롤이다. 예제를 통해 실습해본 후 내 프로젝트에 맞게 변경했다. 서버에

class-programming.tistory.com

codentrick.com/load-more-recyclerview-bottom-progressbar/

 

Load More RecyclerView and Bottom ProgressBar

If you have a project with a requiement that get list user from webservice then use RecyclerView to show users. When scroll RecyclerView to the end you need connect to webservice to get more data and update RecyclerView. When connect to webservice to get m

codentrick.com

 

   -> Retrofit2 + Rxjava2

 

duzi077.tistory.com/186

 

[Rxjava2] Rxjava2 를 이용한 로또 번호 당첨 결과 확인하기

이번주 로또 번호를 가져와서 랜덤으로 생성한 번호와 비교하여 당첨결과를 확인하는 예제를 만들어보겠습니다. 결과물을 먼저 보자면 아래와 같습니다. 목차 0. 환경설정 1. 네트워크에서 로또

duzi077.tistory.com

tiii.tistory.com/11

 

Retrofit2 + okhttp3 + Rxandroid 사용법

OkHttp3에서 달라진 쿠키스토어 사용방법은 이후에 작성된 아래 포스트를 참고해 주세요. http://tiii.tistory.com/13 Retrofit과 OkHttp 는 Square社(https://square.github.io/)에서 만든 오픈라이브러리 입니..

tiii.tistory.com

beomseok95.tistory.com/59

 

안드로이드의 RxJava 활용 - 11( REST API를 활용한 네트워크,Retrofit2 + OkHttp)

본 내용은 필자가 학습한 내용을 정리하는 내용입니다. 대부분 의 내용이 아래 책의 내용이므로 원서를 구매해서 직접보시는걸 추천드립니다! RxJava 프로그래밍 리액티브 프로그래밍 기초부터

beomseok95.tistory.com