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 the explanation.Let’s get started with the coding part of the pagination with recyclerview in android.
1. Setup dependency for the pagination with recyclerview
Add recyclerview, retrofit, and glide dependencies into the app
build.gradlefile.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 withretrofit to load data from RESTAPI.
Add Internet Permission to your Application inAndroidManifest.xml
<uses-permission android:name=”android.permission.INTERNET”/>
Create the Retrofit instance
We need to create the Retrofit instance to send the network requests. we need to use the Retrofit Builder class and specify the base URL for the service.
public class ClientApi { private static Retrofit retrofit = null; public static Retrofit getClient() { if (retrofit == null) { retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .baseUrl("https://velmm.com/apis/") .build(); } return retrofit; } }
Setting Up the Retrofit Interface
Retrofit provides the list of annotations for each HTTP method:
@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>.interface MovieService { @GET("volley_array.json") fun getMovies() : Call<List<Movie>> }
In my JSON response, I have a list of movies with names, and properties. So, My Model class will be like a Movie as class name and name, year, and director are properties.
data class Movie {ot @SerializedName("title") private var title : String @SerializedName("image") private var imageUrl : String }
3. Setup pagination with recyclerview
First, Add RecyclerView into activity_main.xml
<?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/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:paddingBottom="24dp"/> <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </FrameLayout>
Recyclerview Adapter for Pagination
Create class PaginationAdapter extending
RecyclerView.Adapter, and then create two RecyclerView.ViewHolder.class MovieViewHolder() — Recyclerview items
class LoadingViewHolder() — Footer ProgressBar used for Pagination
public class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context; private List<Movie> movieList; private static final int LOADING = 0; private static final int ITEM = 1; private boolean isLoadingAdded = false; public PaginationAdapter(Context context) { this.context = context; movieList = new LinkedList<>(); } public void setMovieList(List<Movie> movieList) { this.movieList = movieList; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder = null; LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch (viewType) { case ITEM: View viewItem = inflater.inflate(R.layout.item_list, parent, false); viewHolder = new MovieViewHolder(viewItem); break; case LOADING: View viewLoading = inflater.inflate(R.layout.item_progress, parent, false); viewHolder = new LoadingViewHolder(viewLoading); break; } return viewHolder; } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { Movie movie = movieList.get(position); switch (getItemViewType(position)) { case ITEM: MovieViewHolder movieViewHolder = (MovieViewHolder) holder; movieViewHolder.movieTitle.setText(movie.getTitle()); Glide.with(context).load(movie.getImageUrl()).apply(RequestOptions.centerCropTransform()).into(movieViewHolder.movieImage); break; case LOADING: LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; loadingViewHolder.progressBar.setVisibility(View.VISIBLE); break; } } @Override public int getItemCount() { return movieList == null ? 0 : movieList.size(); } @Override public int getItemViewType(int position) { return (position == movieList.size() - 1 && isLoadingAdded) ? LOADING : ITEM; } public void addLoadingFooter() { isLoadingAdded = true; add(new Movie()); } public void removeLoadingFooter() { isLoadingAdded = false; int position = movieList.size() - 1; Movie result = getItem(position); if (result != null) { movieList.remove(position); notifyItemRemoved(position); } } public void add(Movie movie) { movieList.add(movie); notifyItemInserted(movieList.size() - 1); } public void addAll(List<Movie> moveResults) { for (Movie result : moveResults) { add(result); } } public Movie getItem(int position) { return movieList.get(position); } public class MovieViewHolder extends RecyclerView.ViewHolder { private TextView movieTitle; private ImageView movieImage; public MovieViewHolder(View itemView) { super(itemView); movieTitle = (TextView) itemView.findViewById(R.id.movie_title); movieImage = (ImageView) itemView.findViewById(R.id.movie_poster); } } public class LoadingViewHolder extends RecyclerView.ViewHolder { private ProgressBar progressBar; public LoadingViewHolder(View itemView) { super(itemView); progressBar = (ProgressBar) itemView.findViewById(R.id.loadmore_progress); } } }
- Kotlin
import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions class PaginationAdapter(private val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var movieList: MutableList<Movie> = mutableListOf() private val LOADING = 0 private val ITEM = 1 private var isLoadingAdded = false fun setMovieList(movieList: List<Movie>) { this.movieList = movieList.toMutableList() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { ITEM -> { val viewItem = inflater.inflate(R.layout.item_list, parent, false) MovieViewHolder(viewItem) } LOADING -> { val viewLoading = inflater.inflate(R.layout.item_progress, parent, false) LoadingViewHolder(viewLoading) } else -> throw IllegalArgumentException("Invalid view type") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (getItemViewType(position)) { ITEM -> { val movie = movieList[position] val movieViewHolder = holder as MovieViewHolder movieViewHolder.movieTitle.text = movie.title Glide.with(context).load(movie.imageUrl).apply(RequestOptions.centerCropTransform()).into(movieViewHolder.movieImage) } LOADING -> { val loadingViewHolder = holder as LoadingViewHolder loadingViewHolder.progressBar.visibility = View.VISIBLE } } } override fun getItemCount(): Int = movieList.size override fun getItemViewType(position: Int): Int { return if (position == movieList.size - 1 && isLoadingAdded) LOADING else ITEM } fun addLoadingFooter() { isLoadingAdded = true add(Movie()) } fun removeLoadingFooter() { isLoadingAdded = false val position = movieList.size - 1 val result = getItem(position) movieList.removeAt(position) notifyItemRemoved(position) } fun add(movie: Movie) { movieList.add(movie) notifyItemInserted(movieList.size - 1) } fun addAll(moveResults: List<Movie>) { moveResults.forEach { add(it) } } fun getItem(position: Int): Movie = movieList[position] inner class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val movieTitle: TextView = itemView.findViewById(R.id.movie_title) val movieImage: ImageView = itemView.findViewById(R.id.movie_poster) } inner class LoadingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val progressBar: ProgressBar = itemView.findViewById(R.id.loadmore_progress) } }
To enable Pagination, we must detect the user reaching the end of the RecyclerView items. For that,
PaginationScrollListener extends the RecyclerView.OnScrollListener and override theonScrolled()method.public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener { private LinearLayoutManager 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) { loadMoreItems(); } } } protected abstract void loadMoreItems(); public abstract boolean isLastPage(); public abstract boolean isLoading(); }
- Kotlin
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView abstract class PaginationScrollListener(private val layoutManager: LinearLayoutManager) : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val visibleItemCount = layoutManager.childCount val totalItemCount = layoutManager.itemCount val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() if (!isLoading() && !isLastPage()) { if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) { loadMoreItems() } } } protected abstract fun loadMoreItems() abstract fun isLastPage(): Boolean abstract fun isLoading(): Boolean }
Finally, Attach pagination with recyclerview to load more items when scrolling.
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); paginationAdapter = new PaginationAdapter(this); recyclerView.setLayoutManager(linearLayoutManager); recyclerView.setAdapter(paginationAdapter); recyclerView.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) { @Override protected void loadMoreItems() { isLoading = true; currentPage += 1; loadNextPage(); } @Override public boolean isLastPage() { return isLastPage; } @Override public boolean isLoading() { return isLoading; } }); loadFirstPage(); } private void loadNextPage() { movieService.getMovies().enqueue(new Callback<List<Movie>>() { @Override public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) { paginationAdapter.removeLoadingFooter(); isLoading = false; List<Movie> results = response.body(); paginationAdapter.addAll(results); if (currentPage != TOTAL_PAGES) paginationAdapter.addLoadingFooter(); else isLastPage = true; } @Override public void onFailure(Call<List<Movie>> call, Throwable t) { t.printStackTrace(); } }); } private void loadFirstPage() { movieService.getMovies().enqueue(new Callback<List<Movie>>() { @Override public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) { List<Movie> results = response.body(); progressBar.setVisibility(View.GONE); paginationAdapter.addAll(results); if (currentPage <= TOTAL_PAGES) paginationAdapter.addLoadingFooter(); else isLastPage = true; } @Override public void onFailure(Call<List<Movie>> call, Throwable t) { } }); }
- Kotlin
val linearLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) val paginationAdapter = PaginationAdapter(this) recyclerView.layoutManager = linearLayoutManager recyclerView.adapter = paginationAdapter recyclerView.addOnScrollListener(object : PaginationScrollListener(linearLayoutManager) { override fun loadMoreItems() { isLoading = true currentPage += 1 loadNextPage() } override fun isLastPage(): Boolean { return isLastPage } override fun isLoading(): Boolean { return isLoading } }) loadFirstPage() private fun loadNextPage() { movieService.getMovies().enqueue(object : Callback<List<Movie>> { override fun onResponse(call: Call<List<Movie>>, response: Response<List<Movie>>) { paginationAdapter.removeLoadingFooter() isLoading = false val results = response.body() paginationAdapter.addAll(results) if (currentPage != TOTAL_PAGES) paginationAdapter.addLoadingFooter() else isLastPage = true } override fun onFailure(call: Call<List<Movie>>, t: Throwable) { t.printStackTrace() } }) } private fun loadFirstPage() { movieService.getMovies().enqueue(object : Callback<List<Movie>> { override fun onResponse(call: Call<List<Movie>>, response: Response<List<Movie>>) { val results = response.body() progressBar.visibility = View.GONE paginationAdapter.addAll(results) if (currentPage <= TOTAL_PAGES) paginationAdapter.addLoadingFooter() else isLastPage = true } override fun onFailure(call: Call<List<Movie>>, t: Throwable) { } }) }
Now, we have implemented the recyclerview with pagination.
You can download the source code for this example on GitHub.