Pagination in android recyclerview using retrofit

 Pagination with Recyclerview allows the user to see the latest content page by page. As we load the next ‘page’ by the time users scroll to the bottom, more content is loaded and available.



When to use Pagination?

I’m sure you have a pretty good idea by now on when to use it. If you have a ton of content that takes too long to load. This can be either from a local database or an API call. Then it makes sense to use Pagination. If you’re pulling from a database, request data in batches (say 20 per request). The same also holds true for an API call.

Now, do with an explanation. Let’s get started on the coding part of the pagination with the recyclerview in android.

1. Setup dependency for the pagination with recyclerview

Add recyclerview, retrofit, and glide dependencies into the app build.gradle file.

implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
implementation 'com.github.bumptech.glide:glide:4.7.1'

2. Prepare data for the recyclerview

In this example, I am using pagination with retrofit to load data from REST API.

Add Internet Permission to your Application in AndroidManifest.xml,

<uses-permission android:name=” android.permission.INTERNET”/>

Create the Retrofit instance





public class instanceApi {

private static Retrofit retrofit = null;

public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://vijayagrocery.com/public/api/v1/")
.build();
}
return retrofit;
}

}

Setting Up the Retrofit Interface

Retrofit provides the list of annotations for each HTTP methods:

@GET@POST@PUT@DELETE@PATCH, or @HEAD.

The endpoints are defined inside of an interface using retrofit annotations to encode details about the parameters and request method. T return value is always a parameterized Call<T>.

public interface ApiService {
@FormUrlEncoded
@POST("notificationList")
Call<Topodderss> getTopRatedMovies(
@Field("user_id") String id,
@Query("page") int pageIndex
);

}


3. Setup pagination with recyclerview



<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/main_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/activity_margin_content"
android:paddingTop="@dimen/activity_margin_content"/>

<ProgressBar
android:id="@+id/main_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>

</FrameLayout>

Recyclerview Adapter for Pagination



public class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int ITEM = 0;
private static final int LOADING = 1;
private static final String BASE_URL_IMG = "https://image.tmdb.org/t/p/w150";

private List<Topodderss.Datuma> Results;
private Context context;

private boolean isLoadingAdded = false;

public PaginationAdapter(Context context) {
this.context = context;
Results = new ArrayList<>();
}

public List<Topodderss.Datuma> getMovies() {
return Results;
}

public void setMovies(List<Topodderss.Datuma> movieResults) {
this.Results = movieResults;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
LayoutInflater inflater = LayoutInflater.from(parent.getContext());

switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, inflater);
break;
case LOADING:
View v2 = inflater.inflate(R.layout.item_progress, parent, false);
viewHolder = new LoadingVH(v2);
break;
}
return viewHolder;
}

@NonNull
private RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
RecyclerView.ViewHolder viewHolder;
View v1 = inflater.inflate(R.layout.item_list, parent, false);
viewHolder = new MovieVH(v1);
return viewHolder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

Topodderss.Datuma result = Results.get(position); // Movie

switch (getItemViewType(position)) {
case ITEM:
final MovieVH movieVH = (MovieVH) holder;

movieVH.mMovieTitle.setText(result.getOrderCode());


movieVH.mYear.setText(
""+result.getDeliveryStatus() // we want the year only

);
movieVH.mMovieDesc.setText(result.getMessage());

/**
* Using Glide to handle image loading.
*/

break;

case LOADING:
// Do nothing
break;


}

}

@Override
public int getItemCount() {
return Results == null ? 0 : Results.size();
}

@Override
public int getItemViewType(int position) {
return (position == Results.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}


/*
Helpers
_________________________________________________________________________________________________
*/

public void add(Topodderss.Datuma r) {
Results.add(r);
notifyItemInserted(Results.size() - 1);
}

public void addAll(List<Topodderss.Datuma> moveResults) {
for (Topodderss.Datuma result : moveResults) {
add(result);
}
}

public void remove(Result r) {
int position = Results.indexOf(r);
if (position > -1) {
Results.remove(position);
notifyItemRemoved(position);
}
}

public void clear() {
isLoadingAdded = false;

}

public boolean isEmpty() {
return getItemCount() == 0;
}


public void addLoadingFooter() {
isLoadingAdded = true;
add(new Topodderss.Datuma());
}

public void removeLoadingFooter() {
isLoadingAdded = false;

int position = Results.size() - 1;
Topodderss.Datuma result = getItem(position);

if (result != null) {
Results.remove(position);
notifyItemRemoved(position);
}
}

public Topodderss.Datuma getItem(int position) {
return Results.get(position);
}


/*
View Holders
_________________________________________________________________________________________________
*/

protected class MovieVH extends RecyclerView.ViewHolder {
private TextView mMovieTitle;
private TextView mMovieDesc;
private TextView mYear; // displays "year | language"
private ImageView mPosterImg;
private ProgressBar mProgress;

public MovieVH(View itemView) {
super(itemView);

mMovieTitle = (TextView) itemView.findViewById(R.id.movie_title);
mMovieDesc = (TextView) itemView.findViewById(R.id.movie_desc);
mYear = (TextView) itemView.findViewById(R.id.movie_year);
mPosterImg = (ImageView) itemView.findViewById(R.id.movie_poster);
mProgress = (ProgressBar) itemView.findViewById(R.id.movie_progress);

itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

Intent i = new Intent(context, DetailsActivity.class);
i.putExtra("mMovieTitle",mMovieTitle.getText());
i.putExtra("mMovieDesc",mMovieDesc.getText());
i.putExtra("mYear",mYear.getText());
//i.putExtra("mPosterImg",extras);
context.startActivity(i);

}
});
}
}


protected class LoadingVH extends RecyclerView.ViewHolder {

public LoadingVH(View itemView) {
super(itemView);
}
}


}

Recyclerview Model class for JSON Response

public class Topodderss {


@SerializedName("responseCode")
@Expose
private String responseCode;
@SerializedName("responseMessage")
@Expose
private String responseMessage;
@SerializedName("response")
@Expose
private Response response;

public String getResponseCode() {
return responseCode;
}

public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}

public String getResponseMessage() {
return responseMessage;
}

public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}

public Response getResponse() {
return response;
}

public void setResponse(Response response) {
this.response = response;
}
public class Response {

@SerializedName("current_page")
@Expose
private Integer currentPage;
@SerializedName("data")
@Expose
private List<Datuma> data = null;
@SerializedName("first_page_url")
@Expose
private String firstPageUrl;
@SerializedName("from")
@Expose
private Integer from;
@SerializedName("last_page")
@Expose
private Integer lastPage;
@SerializedName("last_page_url")
@Expose
private String lastPageUrl;
@SerializedName("next_page_url")
@Expose
private String nextPageUrl;
@SerializedName("path")
@Expose
private String path;
@SerializedName("per_page")
@Expose
private Integer perPage;
@SerializedName("prev_page_url")
@Expose
private String prevPageUrl;
@SerializedName("to")
@Expose
private Integer to;
@SerializedName("total")
@Expose
private Integer total;

public Integer getCurrentPage() {
return currentPage;
}

public void setCurrentPage(Integer currentPage) {
this.currentPage = currentPage;
}

public List<Datuma> getData() {
return data;
}

public void setData(List<Datuma> data) {
this.data = data;
}

public String getFirstPageUrl() {
return firstPageUrl;
}

public void setFirstPageUrl(String firstPageUrl) {
this.firstPageUrl = firstPageUrl;
}

public Integer getFrom() {
return from;
}

public void setFrom(Integer from) {
this.from = from;
}

public Integer getLastPage() {
return lastPage;
}

public void setLastPage(Integer lastPage) {
this.lastPage = lastPage;
}

public String getLastPageUrl() {
return lastPageUrl;
}

public void setLastPageUrl(String lastPageUrl) {
this.lastPageUrl = lastPageUrl;
}

public String getNextPageUrl() {
return nextPageUrl;
}

public void setNextPageUrl(String nextPageUrl) {
this.nextPageUrl = nextPageUrl;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public Integer getPerPage() {
return perPage;
}

public void setPerPage(Integer perPage) {
this.perPage = perPage;
}

public String getPrevPageUrl() {
return prevPageUrl;
}

public void setPrevPageUrl(String prevPageUrl) {
this.prevPageUrl = prevPageUrl;
}

public Integer getTo() {
return to;
}

public void setTo(Integer to) {
this.to = to;
}

public Integer getTotal() {
return total;
}

public void setTotal(Integer total) {
this.total = total;
}
}

public static class Datuma {

@SerializedName("id")
@Expose
private Integer id;
@SerializedName("order_id")
@Expose
private Integer orderId;
@SerializedName("order_status")
@Expose
private Integer orderStatus;
@SerializedName("delivery_status")
@Expose
private Integer deliveryStatus;
@SerializedName("order_code")
@Expose
private String orderCode;
@SerializedName("message")
@Expose
private String message;
@SerializedName("user_id")
@Expose
private Integer userId;
@SerializedName("read")
@Expose
private Integer read;
@SerializedName("type")
@Expose
private String type;
@SerializedName("read_all")
@Expose
private Integer readAll;
@SerializedName("created_at")
@Expose
private String createdAt;
@SerializedName("updated_at")
@Expose
private String updatedAt;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getOrderId() {
return orderId;
}

public void setOrderId(Integer orderId) {
this.orderId = orderId;
}

public Integer getOrderStatus() {
return orderStatus;
}

public void setOrderStatus(Integer orderStatus) {
this.orderStatus = orderStatus;
}

public Integer getDeliveryStatus() {
return deliveryStatus;
}

public void setDeliveryStatus(Integer deliveryStatus) {
this.deliveryStatus = deliveryStatus;
}

public String getOrderCode() {
return orderCode;
}

public void setOrderCode(String orderCode) {
this.orderCode = orderCode;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public Integer getRead() {
return read;
}

public void setRead(Integer read) {
this.read = read;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public Integer getReadAll() {
return readAll;
}

public void setReadAll(Integer readAll) {
this.readAll = readAll;
}

public String getCreatedAt() {
return createdAt;
}

public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}

public String getUpdatedAt() {
return updatedAt;
}

public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}

}
}
To enable Pagination, we must detect the user reaching the end of the RecyclerView items. For that, PaginationScrollListener extends the RecyclerView.OnScrollListener and override the onScrolled() method.

public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {

LinearLayoutManager layoutManager;

/**
* Supporting only LinearLayoutManager for now.
*
* @param layoutManager
*/
public PaginationScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);

int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();

if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0
&& totalItemCount >= getTotalPageCount()) {
loadMoreItems();
}
}

}

protected abstract void loadMoreItems();

public abstract int getTotalPageCount();

public abstract boolean isLastPage();

public abstract boolean isLoading();

}


Finally, Attach pagination with recyclerview to load more items when scroll.

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

PaginationAdapter adapter;
LinearLayoutManager linearLayoutManager;

RecyclerView rv;
ProgressBar progressBar;

private static final int PAGE_START = 1;
private boolean isLoading = false;
private boolean isLastPage = false;
// limiting to 5 for this tutorial, since total pages in actual API is very large. Feel free to modify.
private int TOTAL_PAGES;
private int currentPage = PAGE_START;
Topodderss topRatedMovies;
private ApiService apiService;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

rv = (RecyclerView) findViewById(R.id.main_recycler);
progressBar = (ProgressBar) findViewById(R.id.main_progress);

adapter = new PaginationAdapter(this);

linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rv.setLayoutManager(linearLayoutManager);

rv.setItemAnimator(new DefaultItemAnimator());

rv.setAdapter(adapter);

rv.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
@Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
if (currentPage <= TOTAL_PAGES)

// mocking network delay for API call
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
loadNextPage();
}
}, 1000);

}

@Override
public int getTotalPageCount() {
return TOTAL_PAGES;
}

@Override
public boolean isLastPage() {
return isLastPage;
}

@Override
public boolean isLoading() {
return isLoading;
}
});

//init service and load data
apiService = MovieApi.getClient().create(ApiService.class);

loadFirstPage();

}


private void loadFirstPage() {
Log.d(TAG, "loadFirstPage: ");

callTopRatedApi().enqueue(new Callback<Topodderss>() {
@Override
public void onResponse(Call<Topodderss> call, Response<Topodderss> response) {
// Got data. Send it to adapter

List<Topodderss.Datuma> results = fetchResults(response);
progressBar.setVisibility(View.GONE);
adapter.addAll(results);

if (currentPage <= TOTAL_PAGES)
adapter.addLoadingFooter();
else isLastPage = true;
}

@Override
public void onFailure(Call<Topodderss> call, Throwable t) {
t.printStackTrace();
// TODO: 08/11/16 handle failure
}
});

}

/**
* @param response extracts List<{@link Result>} from response
* @return
*/
private List<Topodderss.Datuma> fetchResults(Response<Topodderss> response) {
Topodderss topRatedMovies = response.body();
TOTAL_PAGES = topRatedMovies.getResponse().getLastPage();

return topRatedMovies.getResponse().getData();
}

private void loadNextPage() {
Log.d(TAG, "loadNextPage: " + currentPage);

callTopRatedApi().enqueue(new Callback<Topodderss>() {
@Override
public void onResponse(Call<Topodderss> call, Response<Topodderss> response) {
adapter.removeLoadingFooter();
isLoading = false;

List<Topodderss.Datuma> results = fetchResults(response);
adapter.addAll(results);

if (currentPage != TOTAL_PAGES) adapter.addLoadingFooter();
else isLastPage = true;
}

@Override
public void onFailure(Call<Topodderss> call, Throwable t) {
t.printStackTrace();
// TODO: 08/11/16 handle failure
}
});
}


private Call<Topodderss> callTopRatedApi() {
return apiService.getTopRatedMovies(
" ",
currentPage
);
}

Now, we have implemented the recyclerview with pagination. Source Code Download Hear



Comments

Popular posts from this blog

How to Integrate or Work with Open Street Map (OSM) in an Android App (Kotlin)

ListView in android with api and volley Network Library in android Studio.

how to implement OpenStreetMap in android