Skip to main content

[Make-A] Movies app using TMDb API Part 5 – Pagination

 

If I were to define it in the most simple way, Pagination is dividing data into smaller parts.

When we fetch movies from TMDb API, we received movies by page. That’s why when fetch movies in Part 4 and you scroll down, you can reach the bottom of your list and that’s it. It’s because what we got was only page 1.

Why? Imagine receiving thousands of movies in one API call. It wouldn’t be very efficient right?

A good solution would be is when you scroll half of your list you should start fetching page 2 and the next page and so on. It depends on your preference, you can start fetching the next page if list is scrolled 25%, 50%, 75%, or 100%. It’s up to you.

Here’s how we can fetch the next page during scrolling:

Table of Contents

  1. Part 1 – Specs and Introduction
  2. Part 2 – Requesting an API Key
  3. Part 3 – Movie List Item Layout Using ConstraintLayout
  4. Part 4 – Networking using Retrofit Library and Image Loading using Glide
  5. Part 5 – Pagination
  6. Part 6 – Sort Movies by Popular, Top Rated and Upcoming
  7. Part 7 – Using CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout for Movie Details Screen
  8. Part 8 – Colors and a Free Launcher Icon Generator
  9. 5 Exercises and Thanks!

 

1. Refactor MoviesRepository.getMovies(…) to accept a page number

1. Open OnGetMoviesCallback interface

public interface OnGetMoviesCallback {

    void onSuccess(int page, List<Movie> movies);

    void onError();
}

2. Open MoviesRepository.class and refactor getMovies(…) method to accept a page and pass the current page through the callback

public void getMovies(int page, final OnGetMoviesCallback callback) {
    Log.d("MoviesRepository", "Next Page = " + page);
    api.getPopularMovies("<YOUR_API_KEY_HERE>", LANGUAGE, page)
            .enqueue(new Callback<MoviesResponse>() {
                @Override
                public void onResponse(@NonNull Call<MoviesResponse> call, @NonNull Response<MoviesResponse> response) {
                    if (response.isSuccessful()) {
                        MoviesResponse moviesResponse = response.body();
                        if (moviesResponse != null && moviesResponse.getMovies() != null) {
                            callback.onSuccess(moviesResponse.getPage(), moviesResponse.getMovies());
                        } else {
                            callback.onError();
                        }
                    } else {
                        callback.onError();
                    }
                }

                @Override
                public void onFailure(Call<MoviesResponse> call, Throwable t) {
                    callback.onError();
                }
            });
}

Our MoviesResponse.class comes has a page field in it which is the current page of the movies that we received. We pass it in the callback so that we will know the next page to fetch. You will see how it’s used in step 2.

 

2. Setup our adapter

Open your MoviesAdapter.class. Add the method

public void appendMovies(List<Movie> moviesToAppend) {
    movies.addAll(moviesToAppend);
    notifyDataSetChanged();
}

 

3. Setup RecyclerView.OnScrollListener()

Open your MainActivitiy.class

public class MainActivity extends AppCompatActivity {

    ...

    private List<Genre> movieGenres;

    private boolean isFetchingMovies;
    private int currentPage = 1;

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

        moviesRepository = MoviesRepository.getInstance();

        moviesList = findViewById(R.id.movies_list);

        setupOnScrollListener();

        getGenres();
    }

    private void setupOnScrollListener() {
        final LinearLayoutManager manager = new LinearLayoutManager(this);
        moviesList.setLayoutManager(manager);
        moviesList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int totalItemCount = manager.getItemCount();
                int visibleItemCount = manager.getChildCount();
                int firstVisibleItem = manager.findFirstVisibleItemPosition();

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    if (!isFetchingMovies) {
                        getMovies(currentPage + 1);
                    }
                }
            }
        });
    }

    private void getGenres() {
        moviesRepository.getGenres(new OnGetGenresCallback() {
            @Override
            public void onSuccess(List<Genre> genres) {
                movieGenres = genres;
                getMovies(currentPage);
            }

            @Override
            public void onError() {
                showError();
            }
        });
    }

    private void getMovies(int page) {
        isFetchingMovies = true;
        moviesRepository.getMovies(page, new OnGetMoviesCallback() {
            @Override
            public void onSuccess(int page, List<Movie> movies) {
                Log.d("MoviesRepository", "Current Page = " + page);
                if (adapter == null) {
                    adapter = new MoviesAdapter(movies, movieGenres);
                    moviesList.setAdapter(adapter);
                } else {
                    adapter.appendMovies(movies);
                }
                currentPage = page;
                isFetchingMovies = false;
            }

            ...
        });
    }

    ...
}

Let me explain things a bit here.

private boolean isFetchingMovies;
private int currentPage = 0;
  • isFetchingMovies – flag that we will use to determine if we are currently fetching the next page. Without this flag, if the we scrolled 50% above it will fetch the same page multiple times and causes duplication.Try commenting out this flag and you will notice that when you scroll, you will see the same movies of next page again and again.
  • currentPage – initialized to page 1. Every time we scrolled half of the list we increment it by one which is the next page.

 

private void getMovies(int page) {
    isFetchingMovies = true;
    moviesRepository.getMovies(page, new OnGetMoviesCallback() {
        @Override
        public void onSuccess(int page, List<Movie> movies) {
            Log.d("MoviesRepository", "Current Page = " + page);
            if (adapter == null) {
                adapter = new MoviesAdapter(movies, movieGenres);
                moviesList.setAdapter(adapter);
            } else {
                adapter.appendMovies(movies);
            }
            currentPage = page;
            isFetchingMovies = false;
        }

        ...
    });
}
  1. isFetchingMovies is set to true to stop fetching movies for the meantime when scrolling.
  2. We refactored onSuccess(…) a bit and now accepts a page. We check first if adapter == null then we initialize it. For succeeding calls to getMovies(…) we just append the movies that we receive from the page that we specified.
  3. We set currentPage = page which was next page that we requested.
  4. We set isFetchingMovies to false to allow for fetching of movies again.

 

4. Let’s test it out

Run the app. You should be able to scroll now and see more movies. 😉 🎉

 

 

Part 6 coming soon. Stay tuned! 😊