MVVM With Retrofit and Recyclerview in Kotlin [Example]

MVVM architecture is a Model-View-ViewModel architecture that removes the tight coupling between each component. Most importantly, in this architecture, the children don’t have a direct reference to the parent, they only have the reference by observables.

What is MVVM?

MVVM stands for ModelViewViewModel.

mvvm architecture diagram
MVVM architecture

Model:

This holds the data of the application. It cannot directly talk to the View. Generally, it’s recommended to expose the data to the ViewModel through Observables

View:

 It represents the UI of the application devoid of any Application Logic. It observes the ViewModel.

ViewModel:

It acts as a link between the Model and the View. It’s responsible for transforming the data from the Model. It provides data streams to the View. It also uses hooks or callbacks to update the View. It’ll ask for the data from the Model.

To learn more about ViewModel check the official document.

viewmodel scope diagram
ViewModel LifeCycle scope

This is all about the MVVM, now let’s move to the implementation part of it.

Adding dependencies for MVVM, Retrofit, and Recyclerview

Add the following dependencies to your app-level build.gradle.

   //ViewModel and livedata
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    //Retrofit
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    //Glide
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    kapt 'com.github.bumptech.glide:compiler:4.12.0'Code language: JavaScript (javascript)

Also, don’t forget to add internet permission in your manifeats.xml file.

 <uses-permission android:name="android.permission.INTERNET"/>Code language: HTML, XML (xml)

Setup the Data Layer

In the data layer, we have to prepare the model for the data, and an API call needs to be implemented. In this example, I am using a Repository pattern to handle the data.

Creating Model class

I am using the “https://www.howtodoandroid.com/movielist.json” API to fetch the data.

For the response data, First, we need to create a model class.

Model.kt

data class Movie(val name: String, val imageUrl: String, val category: String, val desc: String)

Setting up Retrofit

Retrofit is a “Type-safe HTTP client for Android and Java”.

I have explained retrofit in another post.

Retrofit android example kotlin[step by step]

First, create the interface for the API call definition.

interface RetrofitService {
    @GET("movielist.json")
    fun getAllMovies(): Call<List<Movie>>
}Code language: PHP (php)

Next, Create the Retrofit service instance using the retrofit.

Both are part of the same class. RetrofitService.kt

interface RetrofitService {
    @GET("movielist.json")
    fun getAllMovies() : Call<List<Movie>>
    
    companion object {
        var retrofitService: RetrofitService? = null
        
        fun getInstance() : RetrofitService {
            if (retrofitService == null) {
                val retrofit = Retrofit.Builder()
                    .baseUrl("https://www.howtodoandroid.com/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                retrofitService = retrofit.create(RetrofitService::class.java)
            }
            return retrofitService!!
        }
    }
}Code language: PHP (php)

Setup Data Repository

I am using a repository pattern to handle the data from API. In the repository class, we need to pass the retrofit service instance to perform the network call. We don’t need to handle the response here in the repository. That will be part of the ViewModel.

MainRepository.kt

class MainRepository constructor(private val retrofitService: RetrofitService) {
    fun getAllMovies() = retrofitService.getAllMovies()
}

Setup the ViewModel

In the ViewModel setup, We need to create a class and extend the ViewModel. ViewModel class has business logic and API call implementations. In the ViewModel constructor, we need to pass the data repository to handle the data.

MainViewModel.kt

class MainViewModel constructor(private val repository: MainRepository)  : ViewModel() {
    
    val movieList = MutableLiveData<List<Movie>>()
    val errorMessage = MutableLiveData<String>()
    
    fun getAllMovies() {
        val response = repository.getAllMovies()
        response.enqueue(object : Callback<List<Movie>> {
            override fun onResponse(call: Call<List<Movie>>, response: Response<List<Movie>>) {
                movieList.postValue(response.body())
            }
            override fun onFailure(call: Call<List<Movie>>, t: Throwable) {
                errorMessage.postValue(t.message)
            }
        })
    }
}

We are using Live data to update the data to UI.

Live Data

Since LiveData respects Android Lifecycle, this means it will not invoke its observer callback unless activity or fragment is received onStart() but did not accept onStop() Adding to this, LiveData will also automatically remove the observer when its host receives onDestroy().

ViewModel Factory

We can not create ViewModel on our own. We need the ViewModelProviders utility provided by Android to create ViewModels.

But ViewModelProviders can only instantiate ViewModels with the no-arg constructor.

So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.

MyViewModelFactory.kt

class MyViewModelFactory constructor(private val repository: MainRepository): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            MainViewModel(this.repository) as T
        } else {
            throw IllegalArgumentException("ViewModel Not Found")
        }
    }
}Code language: HTML, XML (xml)

Setting up the UI

In the UI part, We need to create an instance of the ViewModel and observe the API response. Based on the API response we need to update the UI.

First, we need to set up the recyclerview in our MainActivity. I have explained about recyclerview in another post.

Recyclerview Android Example

Create recyclerview in our main XML file.

activity_main.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/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        tools:listitem="@layout/adapter_movie"
        tools:itemCount="5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>Code language: HTML, XML (xml)

Also, create an adapter for the recyclerview to set all the items into the recyclerview.

MainAdapter.kt:

class MainAdapter: RecyclerView.Adapter<MainViewHolder>() {
    var movies = mutableListOf<Movie>()
    fun setMovieList(movies: List<Movie>) {
        this.movies = movies.toMutableList()
        notifyDataSetChanged()
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = AdapterMovieBinding.inflate(inflater, parent, false)
        return MainViewHolder(binding)
    }
    override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
        val movie = movies[position]
       holder.binding.name.text = movie.name
        Glide.with(holder.itemView.context).load(movie.imageUrl).into(holder.binding.imageview)
    }
    override fun getItemCount(): Int {
        return movies.size
    }
}
class MainViewHolder(val binding: AdapterMovieBinding) : RecyclerView.ViewHolder(binding.root) {
}Code language: HTML, XML (xml)

item layout for the adapter. adapter_movie.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_margin="8dp"
    android:elevation="8dp"
    app:cardCornerRadius="8dp">
    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/imageview"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:layout_height="200dp" />
    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center_vertical"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        tools:text="@tools:sample/full_names"
        android:background="#E1303F9F"
        android:paddingStart="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</androidx.cardview.widget.CardView>Code language: HTML, XML (xml)

Once, all the recyclerview setup is completed. we need to create a ViewModel instance and call the API.

val viewModel = ViewModelProvider(this, MyViewModelFactory(MainRepository(retrofitService))).get(MainViewModel::class.java)
        
viewModel.getAllMovies()Code language: JavaScript (javascript)

Finally, we need to observe the response from the API and update the UI.

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    private lateinit var binding: ActivityMainBinding
    lateinit var viewModel: MainViewModel
    private val retrofitService = RetrofitService.getInstance()
    val adapter = MainAdapter()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        viewModel = ViewModelProvider(this, MyViewModelFactory(MainRepository(retrofitService))).get(MainViewModel::class.java)
        binding.recyclerview.adapter = adapter
        viewModel.movieList.observe(this, Observer {
            Log.d(TAG, "onCreate: $it")
            adapter.setMovieList(it)
        })
        viewModel.errorMessage.observe(this, Observer {
        })
        viewModel.getAllMovies()
    }
}

That’s all. Thanks for reading. You can download this example in GITHUB.


Posted

in

,

by

Comments

15 responses to “MVVM With Retrofit and Recyclerview in Kotlin [Example]”

  1. Udayakumar R

    I need code snippet for all concepts in android advanced level.

  2. Udayakumar R

    Thanks for the code snippet

  3. shruti

    Incomplete code !! No databinding package is present,

    1. I am using viewbinding in that example. I will explain about viewbinding in my another post.

    2. Aghogho

      Provide the databinding by by placing buildFeatures {
      viewBinding true
      }
      in the app gradle. You will be able to initialize binding in MainAdapter and MainActivity class afterwards.

    3. faiz

      use this
      buildFeatures{
      dataBinding = true
      viewBinding = true
      }

  4. Hector

    Thank you very much for this

  5. NK

    How to write UI test case for this?

  6. Priyank

    what is AdapterMovieBinding in
    binding = AdapterMovieBinding.inflate line?

    Is it any adapter or row_layout?

    1. it’s an adapter_movie.xml layout file. I am using View binding.

  7. Anon

    You shouldn’t expose a mutableLiveData to the consumers, make it private and expose a public LiveData of them instead.

  8. Gaurav

    Why can’t we handle API response in Repository ?

  9. Himanshu

    Where to create ViewModel instance?

    1. In your main activity
      viewModel = ViewModelProvider(this, MyViewModelFactory(MainRepository(retrofitService))).get(MainViewModel::class.java)

Leave a Reply

Your email address will not be published. Required fields are marked *