Android – Build a Movie App using Retrofit and MVVM Architecture with Kotlin
Model — View — ViewModel (MVVM) is the industry-recognized software architecture pattern that overcomes all drawbacks of MVP and MVC design patterns. MVVM suggests separating the data presentation logic(Views or UI) from the core business logic part of the application.
The separate code layers of MVVM are:
- Model: This layer is responsible for the abstraction of the data sources. Model and ViewModel work together to get and save the data.
- View: The purpose of this layer is to inform the ViewModel about the user’s action. This layer observes the ViewModel and does not contain any kind of application logic.
- ViewModel: It exposes those data streams which are relevant to the View. Moreover, it serves as a link between the Model and the View.
In this article, we will learn how we can build a simple movie detail app using MVVM architecture and Kotlin language. To build this application, we need MVVM architecture and Retrofit Library. Retrofit is a third-party library that helps us to make a network request in android. We will fetch data from The Movie Database Website (TMDB). To use this website, you need to log in and generate an API key. In this example, we are fetching the list of popular movies which we can get from this link.
Step by Step Implementation
Step 1: Create a New Project in Android Studio
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.
Step 2: Add Retrofit dependencies in Build.gradle(app) file
Kotlin
// retrofit implementation( "com.squareup.retrofit2:retrofit:2.9.0" ) implementation 'com.squareup.retrofit2:converter-gson:2.3.0' |
Add View Model and Live Data dependencies in Build.gradle(app) file
Kotlin
def lifecycle_version = "2.6.0-alpha01" // ViewModel implementation( "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" ) // ViewModel utilities for Compose implementation( "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" ) // LiveData implementation( "androidx.lifecycle:lifecycle-live data-ktx:$lifecycle_version" ) |
Add Glide Dependency in Build.gradle(app) file
Kotlin
implementation 'com.github.bumptech.glide:glide:4.13.2' |
Glide library helps in image processing in android.
Step 3: Enable View Binding
To enable view binding add this code inside the android {} block in build.gradle(app) file
Kotlin
buildFeatures { viewBinding = true } |
Step 4: Generate data classes
Copy the response that you get from API
Right-click on the root package and select New->Kotlin-data class from JSON
If you don’t have this plugin, go to File -> Settings -> Plugin and install JSON to Kotlin Plugin. Copy the JSON result and paste it. Give this file a suitable name after that two data classes will be generated.
Kotlin
data class Movies( val page: Int, val results: List<Result>, val total_pages: Int, val total_results: Int ) |
Kotlin
data class Result( val adult: Boolean, val backdrop_path: String, val genre_ids: List<Int>, val id: Int, val original_language: String, val original_title: String, val overview: String, val popularity: Double, val poster_path: String, val release_date: String, val title: String, val video: Boolean, val vote_average: Double, val vote_count: Int ) |
Right Click on the Root package and create an interface MovieApi
Kotlin
interface MovieApi { @GET ( "popular?" ) fun getPopularMovies( @Query ( "api_key" ) api_key : String) : Call<Movies> } |
Create a Retrofit Instance
Kotlin
object RetrofitInstance { val api : MovieApi by lazy { Retrofit.Builder() .baseUrl( "https://api.themoviedb.org/3/movie/" ) .addConverterFactory(GsonConverterFactory.create()) .build() .create(MovieApi:: class .java) } } |
Step 5: Design layout Files
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. Comments are added inside the code to understand the code in more detail.
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" tools:context = ".MainActivity" > < androidx.recyclerview.widget.RecyclerView android:id = "@+id/rv_movies" android:layout_width = "match_parent" android:layout_height = "match_parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintBottom_toBottomOf = "parent" tools:listitem = "@layout/movie_layout" > </ androidx.recyclerview.widget.RecyclerView > </ androidx.constraintlayout.widget.ConstraintLayout > |
Since we are using recycler view we need to create a new layout file for that go to res->layout and create a new Layout resource file named movie_layout.
movie_layout.xml file
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "wrap_content" android:layout_height = "wrap_content" xmlns:app = "http://schemas.android.com/apk/res-auto" > < ImageView android:id = "@+id/movieImage" android:layout_width = "200dp" android:layout_height = "200dp" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintEnd_toEndOf = "parent" android:scaleType = "fitCenter" android:src = "@color/teal_200" > </ ImageView > < TextView android:id = "@+id/movieName" android:layout_width = "wrap_content" android:layout_height = "wrap_content" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintTop_toBottomOf = "@id/movieImage" android:textSize = "30dp" android:text = "Movie Name" android:textAlignment = "center" android:textColor = "@color/black" android:textStyle = "bold" android:layout_marginTop = "5dp" > </ TextView > </ androidx.constraintlayout.widget.ConstraintLayout > |
Step 6: Create a Movie Adapter class for RecyclerView
Kotlin
class MovieAdapter : RecyclerView.Adapter<MovieAdapter.ViewHolder>() { private var movieList = ArrayList<Result>() fun setMovieList(movieList : List<Result>){ this .movieList = movieList as ArrayList<Result> notifyDataSetChanged() } class ViewHolder(val binding : MovieLayoutBinding) : RecyclerView.ViewHolder(binding.root) {} override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( MovieLayoutBinding.inflate( LayoutInflater.from( parent.context ) ) ) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { Glide.with(holder.itemView) .load( "https://image.tmdb.org/t/p/w500" +movieList[position].poster_path) .into(holder.binding.movieImage) holder.binding.movieName.text = movieList[position].title } override fun getItemCount(): Int { return movieList.size } |
Step 7: Create Movie View Model
Since we are using MVVM architecture we need to create a View Model Class with live-data in it.
Kotlin
class MovieViewModel : ViewModel() { private var movieLiveData = MutableLiveData<List<Result>>() fun getPopularMovies() { RetrofitInstance.api.getPopularMovies( "69d66957eebff9666ea46bd464773cf0" ).enqueue(object : Callback<Movies>{ override fun onResponse(call: Call<Movies>, response: Response<Movies>) { if (response.body()!= null ){ movieLiveData.value = response.body()!!.results } else { return } } override fun onFailure(call: Call<Movies>, t: Throwable) { Log.d( "TAG" ,t.message.toString()) } }) } fun observeMovieLiveData() : LiveData<List<Result>> { return movieLiveData } } |
Step 8: Working with the MainActivity.kt file
Go to the MainActivity.kt file and refer to the following code. Below is the code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail.
Kotlin
class MainActivity : AppCompatActivity() { private lateinit var binding : ActivityMainBinding private lateinit var viewModel: MovieViewModel private lateinit var movieAdapter : MovieAdapter override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) prepareRecyclerView() viewModel = ViewModelProvider( this )[MovieViewModel:: class .java] viewModel.getPopularMovies() viewModel.observeMovieLiveData().observe( this , Observer { movieList -> movieAdapter.setMovieList(movieList) }) } private fun prepareRecyclerView() { movieAdapter = MovieAdapter() binding.rvMovies.apply { layoutManager = GridLayoutManager(applicationContext, 2 ) adapter = movieAdapter } } } |
Output:
Contact Us