MVVM in Android – ViewModels, ViewModelScope, Retrofit all with a Simple Example.

By | June 6, 2021

Let’s learn how to implement MVVM in Android.

It’s actually really simple.

ViewModel is just another class that that implements the main Business Logic.
ViewModel is a middle man between your view and your Data class(Repo/any Data Provider).

It can be represented simply like this.

MVVM Android

Here the View just displays the data.
ViewModel doesn’t know where the data is coming from which makes it reusable.
Repo takes care of the data, it can be from the Server, Local DB or files or somewhere else.

Here is the Key

Since we are using ViewModel and LiveData which are Activity lifecycle aware, it will lead to fewer crashes and memory leaks.


Aim

Here our aim is to fetch a list of users from a webservice and show it in a ListView.

Below is the webservice we are going to consume.

https://jsonplaceholder.typicode.com/users


#1. Create Model classes for the Web-Service response.

You can simply grap the response from the websservice and paste it in the below website to generate Java Models.

https://www.jsonschema2pojo.org/

Once you have all the java moodels, it is easy to convert it to Kotlin code.
Simply create corresponding Kotlin classes and paste the java code onto it.
Android Studio will automatically convert all the java code to Kotlin.
As simple as that.

The Model classes for this demo is available here if you want to grab it quickly.

Model classes for above webservice

Okay now we have the model classes.

In addition to the above classes, we also have an ErrorModel class which is used for the errors in the Service.

data class UserError(
    val code: Int,
    val message: String
)

#2. Create the Repo Class.

Our Repo class will use Retrofit to call the webservice and parse it using gson.

Add the below dependencies in your project build.gradle and sync the project.

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

Now let’s create the following classes.


1. RetrofitUtil.kt // Base Retrofit class
2. UserRepo.kt. // Repo class for User Service
3. Constants.kt. // Class holding the app constants
4. UserInterface.kt // Retrofit User interface class
5. UsersCallback.kt // interface to get the callback from the repo to the ViewModel.


Constants

This calls holds our constants.

package com.coderzheaven.tutorialprojects.constants

class Constants {

    companion object {

        const val BASE_URL = "https://jsonplaceholder.typicode.com/"
        const val USERS = "users"

        // Errors
        const val UNKNOWN_ERROR = "Unknown Error"
        const val UNKNOWN_ERROR_CODE = 100
        const val USER_LOAD_FAILURE = 101

    }
}

RetrofitUtil

This class will create the Retrofit builder object for the service calls.

package com.coderzheaven.tutorialprojects.repo

import com.coderzheaven.tutorialprojects.constants.Constants.Companion.BASE_URL
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RetrofitUtil {

    companion object {

        private val client = OkHttpClient.Builder().build()

        private val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build()

        fun <T> buildService(service: Class<T>): T {
            return retrofit.create(service)
        }

    }
}

UserInterface

This class is for the UserWebService which is needed by Retrofit.

package com.coderzheaven.tutorialprojects.`interface`

import com.coderzheaven.tutorialprojects.models.User
import retrofit2.Call
import retrofit2.http.GET

interface UsersInterface {

    @GET(USERS)
    fun getUsers(): Call<List<User>>

}

The Callback interface

UsersCallback

The callback class which is passsed to the repo constructor to get the callback to the viewmodel.

package com.coderzheaven.tutorialprojects.callback

import com.coderzheaven.tutorialprojects.models.User
import com.coderzheaven.tutorialprojects.models.UserError

interface UsersCallback {

    fun onSuccess(users: List<User>)
    fun onFailed(userError: UserError)
    fun onLoading(loading: Boolean)

}

UserRepo

Class which calls the webservice and get the data, then it returns the data back to the ViewModel with the help of the interface we send in the constructor.
Not to be confused with the “UsersInterface” which is associated with Retrofit.

package com.coderzheaven.tutorialprojects.repo

import com.coderzheaven.tutorialprojects.`interface`.UsersInterface
import com.coderzheaven.tutorialprojects.callback.UsersCallback
import com.coderzheaven.tutorialprojects.models.User
import com.coderzheaven.tutorialprojects.models.UserError
import com.coderzheaven.tutorialprojects.constants.Constants.Companion.UNKNOWN_ERROR
import com.coderzheaven.tutorialprojects.constants.Constants.Companion.UNKNOWN_ERROR_CODE
import com.coderzheaven.tutorialprojects.constants.Constants.Companion.USER_LOAD_FAILURE
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class UsersRepo(callback: UsersCallback) : Callback<List<User>> {

    private val usersCallback: UsersCallback = callback
    private val userError = UserError(UNKNOWN_ERROR_CODE, UNKNOWN_ERROR)

    fun getAllUsers() {
        usersCallback.onLoading(true)
        val request = RetrofitUtil.buildService(UsersInterface::class.java)
        val call = request.getUsers()
        call.enqueue(this)
    }

    override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
        usersCallback.onLoading(false)
        if (null == response.body()) {
            usersCallback.onFailed(userError)
            return
        }
        if (response.isSuccessful) {
            usersCallback.onSuccess(response.body()!!)
            return
        }
        usersCallback.onFailed(userError)
    }

    override fun onFailure(call: Call<List<User>>, t: Throwable) {
        val userError = UserError(USER_LOAD_FAILURE, t.message.toString())
        usersCallback.onFailed(userError)
        usersCallback.onLoading(false)
    }

}

Okay. Now our Repo is ready.

Let’s create the ViewModel now.
Create a class named “UsersViewModel“.

  • UsersViewModel has three MutableLiveData variables which will be observed by the Activity/Fragment.
  • userList will be notified when the service is returned with a proper user list.
    userError will be notified when there is some error from the service.
    userLoading will be notified when the service starts loading and ends loading.

In the above Repo class when the service is returned, the below method is called

    usersCallback.onSuccess(response.body()!!)

which will return the calling UsersViewModel onSuccess method.
Similarly for the “onFailed” and “onLoading” callbacks.

override fun onSuccess(usersList: List<User>) {
    userList.postValue(usersList)
}

UsersViewModel

package com.coderzheaven.tutorialprojects.view_model

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.coderzheaven.tutorialprojects.callback.UsersCallback
import com.coderzheaven.tutorialprojects.models.User
import com.coderzheaven.tutorialprojects.models.UserError
import com.coderzheaven.tutorialprojects.repo.UsersRepo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class UsersViewModel(application: Application) : AndroidViewModel(application), UsersCallback {

    var userList: MutableLiveData<List<User>> = MutableLiveData()
    var userError: MutableLiveData<UserError> = MutableLiveData()
    var userLoading: MutableLiveData<Boolean> = MutableLiveData()

    fun getUsers() {
        if (userLoading.value == true) {
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            UsersRepo(this@UsersViewModel).getAllUsers()
        }
    }

    override fun onSuccess(usersList: List<User>) {
        userList.postValue(usersList)
    }

    override fun onFailed(error: UserError) {
        userError.postValue(error)
    }

    override fun onLoading(loading: Boolean) {
        userLoading.postValue(loading)
    }

}

Every ViewModel has a viewModelScope which you can use for Threading.
Here we are using the Default Thread property when calling the getUsers.

Now Let’s switch to the UI part.

UI

We are going to write an Activty where all the incoming data is displayed.

package com.coderzheaven.tutorialprojects.view

import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.coderzheaven.tutorialprojects.R
import com.coderzheaven.tutorialprojects.adapter.ListAdapter
import com.coderzheaven.tutorialprojects.models.User
import com.coderzheaven.tutorialprojects.models.UserError
import com.coderzheaven.tutorialprojects.view_model.UsersViewModel

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private lateinit var mListRecyclerView: RecyclerView
    private lateinit var progressBar: ProgressBar
    private lateinit var btnLoad: Button
    private lateinit var tvErrorMessage: TextView

    private lateinit var viewModel: UsersViewModel
    private var mAdapter: ListAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "MVVM"
        findViews()
        showErrorMessage(null)
        setListeners()
    }

    private fun findViews() {
        mListRecyclerView = findViewById(R.id.list_recycler_view)
        btnLoad = findViewById(R.id.btnLoad)
        progressBar = findViewById(R.id.progress)
        tvErrorMessage = findViewById(R.id.tvErrorMessage)
        btnLoad.setOnClickListener(this)
    }

    private fun setListeners() {
        viewModel = ViewModelProvider(this).get(UsersViewModel::class.java)
        viewModel.userList.observe(this, Observer(onUsersListReceived()))
        viewModel.userError.observe(this, Observer(onUserError()))
        viewModel.userLoading.observe(this, Observer(onLoading()))
        viewModel.getUsers()
    }

    private fun onUsersListReceived() = { users: List<User> ->
        setList(users)
    }

    private fun onLoading() = { loading: Boolean ->
        showLoading(loading)
    }

    private fun onUserError() = { userError: UserError ->
        showErrorMessage(userError.message)
    }

    private fun showErrorMessage(message: String?) {
        tvErrorMessage.visibility = if (null != message) View.VISIBLE else View.GONE
        tvErrorMessage.text = message
    }

    private fun showLoading(show: Boolean) {
        progressBar.visibility = if (show) View.VISIBLE else View.GONE
    }

    private fun setList(users: List<User>) {
        if (null != mAdapter) {
            mAdapter?.notifyDataSetChanged()
            return
        }
        mAdapter = ListAdapter(users)
        mListRecyclerView.adapter = mAdapter
        mListRecyclerView.layoutManager = LinearLayoutManager(this)
    }

    override fun onClick(v: View?) {
        if (v!!.id == R.id.btnLoad) {
            showErrorMessage(null)
            viewModel.getUsers()
        }
    }
}

Here the setListeners() method registers the ViewModel and the observers.

viewModel = ViewModelProvider(this).get(UsersViewModel::class.java)
viewModel.userList.observe(this, Observer(onUsersListReceived()))
viewModel.userError.observe(this, Observer(onUserError()))
viewModel.userLoading.observe(this, Observer(onLoading()))
viewModel.getUsers()

The UsersiewModel has the LiveData variables such as the userList, userError and the userLoading.
When the viewModel calls postValue in one of the values based on the response from the Server, the observers in the MainActivity will be Notified.

The Observers here are “onUsersListReceived”, “onUserError” and “onLoading” respectively.

Once we revceive the data in the MainActivity, we can easily update the UI and thats it!!!.

We have mastered the MVVM pattern in Android.

The code for the RecyclerView adapter and MainActivity layout is in the github repo below.

MVVM Android

MVVM Android

Source Code

https://github.com/MrVipinVijayan/AndroidTutorialsMVVM

Please leave your valuable comments below.

Thanks for reading.

Leave a Reply

Your email address will not be published.