Make a Movies App Using TMDb API in Kotlin Part 4 - Popular, Top Rated, and Upcoming Movies

For offline viewing, get the eBook (PDF & ePUB) version + source code here.

This chapter will be quite easy. We will just replicate what we did for popular movies in the previous chapter for top rated and upcoming movies.

Show Top Rated Movies

  1. Open your Api interface and let’s add a new endpoint for fetching top rated movies.

     interface Api {
    
         ...
    
         @GET("movie/top_rated")
         fun getTopRatedMovies(
             @Query("api_key") apiKey: String = "YOUR_API_KEY_HERE",
             @Query("page") page: Int
         ): Call<GetMoviesResponse>
     }
    

    As you can see, getPopularMovies() and getTopRatedMovies() are basically the same. The only difference is that getTopRatedMovies() has a different endpoint - @GET("movie/top_rated").

  2. Open your MoviesRepository and add a new method called getTopRatedMovies().

     object MoviesRepository {
    
         ...
    
         fun getTopRatedMovies(
             page: Int = 1,
             onSuccess: (movies: List<Movie>) -> Unit,
             onError: () -> Unit
         ) {
             api.getTopRatedMovies(page = page)
                 .enqueue(object : Callback<GetMoviesResponse> {
                     override fun onResponse(
                         call: Call<GetMoviesResponse>,
                         response: Response<GetMoviesResponse>
                     ) {
                         if (response.isSuccessful) {
                             val responseBody = response.body()
    
                             if (responseBody != null) {
                                 onSuccess.invoke(responseBody.movies)
                             } else {
                                 onError.invoke()
                             }
                         } else {
                             onError.invoke()
                         }
                     }
    
                     override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                         onError.invoke()
                     }
                 })
         }
     }
    
  3. Open your activity_main.xml and add a new RecyclerView for top rated movies.

     <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
    
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical">
    
             ...
    
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
                 android:layout_marginTop="16dp"
                 android:layout_marginEnd="16dp"
                 android:text="@string/top_rated"
                 android:textColor="@android:color/white"
                 android:textSize="18sp"
                 android:textStyle="bold" />
    
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
                 android:layout_marginEnd="16dp"
                 android:text="@string/highest_rated_movies" />
    
             <androidx.recyclerview.widget.RecyclerView
                 android:id="@+id/top_rated_movies"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
                 android:clipToPadding="false"
                 android:paddingStart="16dp"
                 android:paddingEnd="16dp" />
    
         </LinearLayout>
    
     </androidx.core.widget.NestedScrollView>
    

    Be sure to wrap your LinearLayout with NestedScrollView. You will use NestedScrollView if it has scrollable views as its children such as a RecyclerView as it will take care of handling nested scrolling for you. If you don’t have scrollable views as children, a normal ScrollView will do.

  4. Open your MainActivity and populate your top rated movies list.

     class MainActivity : AppCompatActivity() {
    
         ...
    
         private lateinit var topRatedMovies: RecyclerView
         private lateinit var topRatedMoviesAdapter: MoviesAdapter
         private lateinit var topRatedMoviesLayoutMgr: LinearLayoutManager
    
         private var topRatedMoviesPage = 1
    
         override fun onCreate(savedInstanceState: Bundle?) {
             ...
    
             topRatedMovies = findViewById(R.id.top_rated_movies)
             topRatedMoviesLayoutMgr = LinearLayoutManager(
                 this,
                 LinearLayoutManager.HORIZONTAL,
                 false
             )
             topRatedMovies.layoutManager = topRatedMoviesLayoutMgr
             topRatedMoviesAdapter = MoviesAdapter(mutableListOf())
             topRatedMovies.adapter = topRatedMoviesAdapter
    
             getPopularMovies()
             getTopRatedMovies()
         }
    
         ...
    
         private fun getTopRatedMovies() {
             MoviesRepository.getTopRatedMovies(
                 topRatedMoviesPage,
                 ::onTopRatedMoviesFetched,
                 ::onError
             )
         }
    
         private fun attachTopRatedMoviesOnScrollListener() {
             topRatedMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
                 override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                     val totalItemCount = topRatedMoviesLayoutMgr.itemCount
                     val visibleItemCount = topRatedMoviesLayoutMgr.childCount
                     val firstVisibleItem = topRatedMoviesLayoutMgr.findFirstVisibleItemPosition()
    
                     if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                         topRatedMovies.removeOnScrollListener(this)
                         topRatedMoviesPage++
                         getTopRatedMovies()
                     }
                 }
             })
         }
    
         private fun onTopRatedMoviesFetched(movies: List<Movie>) {
             topRatedMoviesAdapter.appendMovies(movies)
             attachTopRatedMoviesOnScrollListener()
         }
    
         ...
     }
    
    

    Be sure to call getTopRatedMovies() in onCreate().

  5. Run the app and now you can see a list of top rated movies!

Show Upcoming Movies

Next up is to show a list of upcoming movies and you’ve probably guessed it, the process will be the same. I suggest doing it on your own first by referencing what you did in the previous section. Then, come back to this section to compare and double check your code.

  1. Open your Api interface and add a new endpoint for upcoming movies.

     interface Api {
    
         ...
    
         @GET("movie/upcoming")
         fun getUpcomingMovies(
             @Query("api_key") apiKey: String = "YOUR_API_KEY_HERE",
             @Query("page") page: Int
         ): Call<GetMoviesResponse>
     }
    
  2. Open your MoviesRepository and add a new method called getUpcomingMovies().

     object MoviesRepository {
    
         ...
    
         fun getUpcomingMovies(
             page: Int = 1,
             onSuccess: (movies: List<Movie>) -> Unit,
             onError: () -> Unit
         ) {
             api.getUpcomingMovies(page = page)
                 .enqueue(object : Callback<GetMoviesResponse> {
                     override fun onResponse(
                         call: Call<GetMoviesResponse>,
                         response: Response<GetMoviesResponse>
                     ) {
                         if (response.isSuccessful) {
                             val responseBody = response.body()
    
                             if (responseBody != null) {
                                 onSuccess.invoke(responseBody.movies)
                             } else {
                                 onError.invoke()
                             }
                         } else {
                             onError.invoke()
                         }
                     }
    
                     override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                         onError.invoke()
                     }
                 })
         }
     }
    
  3. Open your activity_main.xml and add a new RecyclerView for upcoming movies.

     <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
    
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical">
    
             ...
    
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
                 android:layout_marginTop="16dp"
                 android:layout_marginEnd="16dp"
                 android:text="@string/upcoming"
                 android:textColor="@android:color/white"
                 android:textSize="18sp"
                 android:textStyle="bold" />
    
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
                 android:layout_marginEnd="16dp"
                 android:text="@string/stay_updated" />
    
             <androidx.recyclerview.widget.RecyclerView
                 android:id="@+id/upcoming_movies"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
                 android:layout_marginBottom="16dp"
                 android:clipToPadding="false"
                 android:paddingStart="16dp"
                 android:paddingEnd="16dp" />
    
         </LinearLayout>
    
     </androidx.core.widget.NestedScrollView>
    
  4. Open your MainActivity and populate your upcoming movies list.

     class MainActivity : AppCompatActivity() {
    
         ...
    
         private lateinit var upcomingMovies: RecyclerView
         private lateinit var upcomingMoviesAdapter: MoviesAdapter
         private lateinit var upcomingMoviesLayoutMgr: LinearLayoutManager
    
         private var upcomingMoviesPage = 1
    
         override fun onCreate(savedInstanceState: Bundle?) {
             ...
    
             upcomingMovies = findViewById(R.id.upcoming_movies)
             upcomingMoviesLayoutMgr = LinearLayoutManager(
                 this,
                 LinearLayoutManager.HORIZONTAL,
                 false
             )
             upcomingMovies.layoutManager = upcomingMoviesLayoutMgr
             upcomingMoviesAdapter = MoviesAdapter(mutableListOf())
             upcomingMovies.adapter = upcomingMoviesAdapter
    
             getPopularMovies()
             getTopRatedMovies()
             getUpcomingMovies()
         }
    
         ...
    
         private fun getUpcomingMovies() {
             MoviesRepository.getUpcomingMovies(
                 upcomingMoviesPage,
                 ::onUpcomingMoviesFetched,
                 ::onError
             )
         }
    
         private fun attachUpcomingMoviesOnScrollListener() {
             upcomingMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
                 override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                     val totalItemCount = upcomingMoviesLayoutMgr.itemCount
                     val visibleItemCount = upcomingMoviesLayoutMgr.childCount
                     val firstVisibleItem = upcomingMoviesLayoutMgr.findFirstVisibleItemPosition()
    
                     if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                         upcomingMovies.removeOnScrollListener(this)
                         upcomingMoviesPage++
                         getUpcomingMovies()
                     }
                 }
             })
         }
    
         private fun onUpcomingMoviesFetched(movies: List<Movie>) {
             upcomingMoviesAdapter.appendMovies(movies)
             attachUpcomingMoviesOnScrollListener()
         }
    
         ...
     }
    

    Be sure to call getUpcomingMovies() in onCreate().

  5. Run the app and enjoy scrolling through the list of different movies.

Before we proceed to the next chapter, I want you to pause, take a moment, and realize you’ve just made a fully functional app! Gone were the days where you were just stuck in tutorials after tutorials without producing something tangible and functional that users can actually use.

Our main screen is looking really good but the users need to know what the movie is all about is when they click one. Head over to Part 5 - Movie Details.

7 ways to become a really good Android developer