Retrofit with ViewModel in Kotlin (Part-2)
I strongly recommend you to read the Part-1 of this article.
Part 1 : https://medium.com/@krish.apk/f9e705e77144
Implementing ViewModel
Steps:
- Setting up a Repository class.
- Creating a ViewModelProviderFactory class.
- Creating a ViewModel class by combining the Repository class and the ViewModelProviderFactory class.
- Merging the ViewModel class into Views and calling the API.
Step 1: Repository class
Repository class is created to enable an application to consume data without worrying about its origin.
MoviesRepository.kt:
import com.example.sitmyseat.api.RetroFitInstance
class MoviesRepository() {
suspend fun getMoviesR() = RetroFitInstance.api.getMovies()
}
→ An API call is created in the above block.
→The suspend function is used to make it run in co-routines.
Step 2: ViewModelProviderFactory class
ViewModelProvider.Factory instantiates ViewModel objects, with or without constructor parameters.
MoviesViewModelProviderFactory.kt :
package com.example.sitmyseat.ui
import MoviesRepository
import MoviesViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class MoviesViewModelProviderFactory(val moviesRepository: MoviesRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MoviesViewModel(moviesRepository) as T
}
}
→ Here, we can pass the argument named moviesRepository, whereas the argument cannot be directly passed through the main ViewModel class.
Step 3: ViewModel class
We are moving onto the major part of our ViewModel implementation.
MoviesViewModel.kt :
package com.example.sitmyseat.ui
import MoviesRepository
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.sitmyseat.models.MovieResponse
import com.example.sitmyseat.utils.Resource
import kotlinx.coroutines.launch
import retrofit2.Response
class MoviesViewModel(val moviesRep: MoviesRepository) : ViewModel() {
val moviesPage: MutableLiveData<Resource<MovieResponse>> = MutableLiveData()
init {
getMoviesVM()
}
private fun getMoviesVM() = viewModelScope.launch {
moviesPage.postValue(Resource.Loading())
val response = moviesRep.getMoviesR()
moviesPage.postValue(handleResponse(response))
}
private fun handleResponse(response: Response<MovieResponse>) : Resource<MovieResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
}
Explanation:
class MoviesViewModel(val moviesRep: MoviesRepository)
→As we cannot directly pass the argument “moviesRep”, we have to create the ProviderFactory class. (converted later in step 4).
val moviesPage: MutableLiveData<Resource<MovieResponse>> = MutableLiveData()
→ liveData acts as a notify-changer whenever the dataset changes.
moviesPage.postValue(Resource.Loading())
→ A Loading class is added to the MutableLiveData. (handled in step 4)
val response = moviesRep.getMoviesR()
moviesPage.postValue(handleResponse(response))
→ The above code will add the response of the API call to the MutableLiveData.
Step 4: Merging
MainActivity.kt :
package com.example.sitmyseat.ui
import MoviesRepository
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.sitmyseat.R
import com.example.sitmyseat.adapters.MoviesAdapter
import com.example.sitmyseat.utils.Resource
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MoviesViewModel
lateinit var moviesAdapter : MoviesAdapter
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val repository = MoviesRepository()
val provider = MoviesViewModelProviderFactory(repository)
viewModel = ViewModelProvider(this, provider).get(MoviesViewModel::class.java)
setRecyclerView(this)
viewModel.moviesPage.observe(this, { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
response.data?.let { newsResponse ->
moviesAdapter.differ.submitList(newsResponse.items)
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Log.e(TAG, "An error occured: $message")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
}
private fun hideProgressBar() {
paginationProgressBar.visibility = View.INVISIBLE
}
private fun showProgressBar() {
paginationProgressBar.visibility = View.VISIBLE
}
fun setRecyclerView(context: Context){
moviesAdapter = MoviesAdapter()
rvMovies.apply {
adapter=moviesAdapter
layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
}
}
}
Explanation:
viewModel=
ViewModelProvider(this,provider).get(MoviesViewModel::class.java)
→ This line converts our providerFactory class into ViewModel by taking the Repository class as an argument.
The Resource file will be unwrapped in the following code:
when(response) {
is Resource.Success -> {
hideProgressBar()
response.data?.let { newsResponse ->
moviesAdapter.differ.submitList(newsResponse.items)
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Log.e(TAG, "An error occured: $message")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
Bonus tip:
Keeping your files and packages well structured will help a lot in terms of the MVVM Architecture.
Conclusion:
Yay, you have completed the article!
Hope you have learnt something. Feel free to drop your thoughts and feedback.
Source code: https://github.com/krish-dev-7/sitMySeat