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

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 official document.

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 in 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'

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

    <uses-permission android:name="android.permission.INTERNET"/>

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 “https://howtodoandroid.com/movielist.json” API to fetch the data.

For the response data, First we need to create 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 “A type-safe HTTP client for Android and Java”.

I have explained about retrofit in my 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>>
}

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://howtodoandroid.com/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                retrofitService = retrofit.create(RetrofitService::class.java)
            }
            return retrofitService!!
        }
    }
}

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 extends the ViewModel. ViewModel class having the 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 ViewModelProviders utility provided by Android to create ViewModels.

But ViewModelProviders can only instantiate ViewModels with 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")
        }
    }
}

Setting up the UI

In the UI part, We need to create instance of the viewmodel and observe the api response. Based on the api response we need to update the UI.

First, we need to setup the recyclerview in our MainActivity. I have explained about recyclerview in my 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>

Also, create adapter for the recyclerview to set all the items into 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) {

}

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>

Once, all the recyclerview setup in completed. we need to create viewmodel instnce and call the api.

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

Finally, we need to observer 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.

Leave a Reply

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