MVVM Architecture - Android Tutorial for Beginners - Step by Step Guide

MVVM Architecture - Android Tutorial for Beginners - Step by Step Guide

In this tutorial, first, we are going to learn about the MVVM architecture in Android, and then we will build a project with MVVM architecture. This tutorial is for beginners who want to get started with the MVVM architecture. As this is for beginners, I have done some simplifications. Let's get started.

We will cover the following in this tutorial:

  • What is MVVM architecture?
  • Set up a new project with Kotlin and other dependencies required.
  • Project Structure.
  • Set up the utils package.
  • Set up the data layer.
  • Set up UI layer, build and run the project.
  • Project Source Code and What Next?

What is MVVM architecture?

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 the direct reference to the parent, they only have the reference by observables.

MVVM Architecture - Android Tutorial for Beginners - Step by Step Guide

The above image is taken from another MindOrks blog. So, the image credit goes to the writer of that blog.

  • Model: It represents the data and the business logic of the Android Application. It consists of the business logic - local and remote data source, model classes, repository.
  • View: It consists of the UI Code(Activity, Fragment), XML. It sends the user action to the ViewModel but does not get the response back directly. To get the response, it has to subscribe to the observables which ViewModel exposes to it.
  • ViewModel: It is a bridge between the View and Model(business logic). It does not have any clue which View has to use it as it does not have a direct reference to the View. So basically, the ViewModel should not be aware of the view who is interacting with. It interacts with the Model and exposes the observable that can be observed by the View.

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

If you are preparing for your next Android Interview, Join our Android Professional Course to learn the latest in Android and land job at top tech companies.

Set up a new project with Kotlin and other dependencies required

Here, we are going to set up the Android Project.

Create a Project

  • Start a new Android Studio Project
  • Select Empty Activity and Next
  • Name: MVVM-Architecture-Android-Beginners
  • Package name: com.mindorks.framework.mvvm
  • Language: Kotlin
  • Finish
  • Your starting project is ready now

Add dependencies

Add the following dependencies in your app level build.gradle.

implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.amitshekhar.android:rx2-android-networking:1.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.2.18'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

Now our project is ready with dependencies.

Project Structure

For the project, we are going to follow a beginner version of MVVM. Our package in the project will look like below:

MVVM Architecture - Android Tutorial for Beginners - Step by Step Guide

Set up the utils package

Now, we can set up our utils package.

We need the enum to represent the UI State. We will create that in the utils package.

package com.mindorks.framework.mvvm.utils

enum class Status {
    SUCCESS,
    ERROR,
    LOADING
}

We need a utility class that will be responsible to communicate the current state of Network Call to the UI Layer. We are naming that as Resource.

So, create a Kotlin data class Resource inside the same utils package and add the following code.

package com.mindorks.framework.mvvm.utils

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {

    companion object {

        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: String, data: T?): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }

    }

}

Our utils package is ready now.

Set up the data layer

Now, in this section, we will set up the data layer.

Create package - data

Create package - model inside the data

Our API JSON response will be like this.

[
  {
    "id": "1",
    "name": "Mrs. Nedra Gerhold",
    "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/to_soham/128.jpg",
    "email": "Lonzo6@hotmail.com"
  },
  {
    "id": "2",
    "name": "Spencer McKenzie",
    "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/irae/128.jpg",
    "email": "Josiah.Hane@gmail.com"
  }
]

Accordingly, we need to create our data class.

Now, create a Kotlin file User inside the model package.

This will consist of data class User like below.

package com.mindorks.framework.mvvm.data.model

import com.google.gson.annotations.SerializedName

data class User(
    @SerializedName("id")
    val id: Int = 0,
    @SerializedName("name")
    val name: String = "",
    @SerializedName("email")
    val email: String = "",
    @SerializedName("avatar")
    val avatar: String = ""
)

Now, we need to set up our Network Layer.

Create package - api inside the data

Then, create an interface ApiService inside the api package and add the following code.

package com.mindorks.framework.mvvm.data.api

import com.mindorks.framework.mvvm.data.model.User
import io.reactivex.Single

interface ApiService {

    fun getUsers(): Single<List<User>>

}

Then, create an ApiServiceImpl class implementing the interface ApiService inside the api package and add the following code.

package com.mindorks.framework.mvvm.data.api

import com.mindorks.framework.mvvm.data.model.User
import com.rx2androidnetworking.Rx2AndroidNetworking
import io.reactivex.Single

class ApiServiceImpl : ApiService {

    override fun getUsers(): Single<List<User>> {
        return Rx2AndroidNetworking.get("https://5e510330f2c0d300147c034c.mockapi.io/users")
            .build()
            .getObjectListSingle(User::class.java)
    }

}

Now, create a class ApiHelper inside the api package and add the following code.

package com.mindorks.framework.mvvm.data.api

class ApiHelper(private val apiService: ApiService) {

    fun getUsers() = apiService.getUsers()

}

Create package - repository inside the data

Now, create a class MainRepository inside the repository package and add the following code.

package com.mindorks.framework.mvvm.data.repository

import com.mindorks.framework.mvvm.data.api.ApiHelper
import com.mindorks.framework.mvvm.data.model.User
import io.reactivex.Single

class MainRepository(private val apiHelper: ApiHelper) {

    fun getUsers(): Single<List<User>> {
        return apiHelper.getUsers()
    }

}

Now, our data layer is ready.

Set up UI layer, build and run the project

Now, in this section, we will setup UI, build the project and run on the device.

Create package - ui

Create package - main inside the ui package

Create package - view inside the main package

Move the MainActivity to the view package

Create package - viewmodel inside the main package

Now, create a Kotlin class MainViewModel inside the same viewmodel package and add the following code.

package com.mindorks.framework.mvvm.ui.main.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.mindorks.framework.mvvm.data.model.User
import com.mindorks.framework.mvvm.data.repository.MainRepository
import com.mindorks.framework.mvvm.utils.Resource
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers

class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {

    private val users = MutableLiveData<Resource<List<User>>>()
    private val compositeDisposable = CompositeDisposable()

    init {
        fetchUsers()
    }

    private fun fetchUsers() {
        users.postValue(Resource.loading(null))
        compositeDisposable.add(
            mainRepository.getUsers()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ userList ->
                    users.postValue(Resource.success(userList))
                }, { throwable ->
                    users.postValue(Resource.error("Something Went Wrong", null))
                })
        )
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.dispose()
    }

    fun getUsers(): LiveData<Resource<List<User>>> {
        return users
    }

}

Here, we have used LiveData, learn more about it here.

Now, let's set up the XML layout.

In the layout folder, update the activity_main.xml with the following code:

<?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=".ui.main.view.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Add item_layout.xml in the layout folder and add the following code:

<?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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <ImageView
        android:id="@+id/imageViewAvatar"
        android:layout_width="60dp"
        android:layout_height="0dp"
        android:padding="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUserName"
        style="@style/TextAppearance.AppCompat.Large"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="4dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="MindOrks" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUserEmail"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/textViewUserName"
        app:layout_constraintTop_toBottomOf="@+id/textViewUserName"
        tools:text="MindOrks" />

</androidx.constraintlayout.widget.ConstraintLayout>

Create package - adapter inside the main package

Now, create a Kotlin class MainAdapter inside the same adapter package and add the following code.

package com.mindorks.framework.mvvm.ui.main.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.mindorks.framework.mvvm.R
import com.mindorks.framework.mvvm.data.model.User
import kotlinx.android.synthetic.main.item_layout.view.*

class MainAdapter(
    private val users: ArrayList<User>
) : RecyclerView.Adapter<MainAdapter.DataViewHolder>() {

    class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: User) {
            itemView.textViewUserName.text = user.name
            itemView.textViewUserEmail.text = user.email
            Glide.with(itemView.imageViewAvatar.context)
                .load(user.avatar)
                .into(itemView.imageViewAvatar)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        DataViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_layout, parent,
                false
            )
        )

    override fun getItemCount(): Int = users.size

    override fun onBindViewHolder(holder: DataViewHolder, position: Int) =
        holder.bind(users[position])

    fun addData(list: List<User>) {
        users.addAll(list)
    }

}

Create package - base inside the ui package

Now, create a Kotlin class ViewModelFactory inside the base package and add the following code.

package com.mindorks.framework.mvvm.ui.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.mindorks.framework.mvvm.data.api.ApiHelper
import com.mindorks.framework.mvvm.data.repository.MainRepository
import com.mindorks.framework.mvvm.ui.main.viewmodel.MainViewModel

class ViewModelFactory(private val apiHelper: ApiHelper) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(MainRepository(apiHelper)) as T
        }
        throw IllegalArgumentException("Unknown class name")
    }

}

Now, we need to complete our MainActivity class.

package com.mindorks.framework.mvvm.ui.main.view

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mindorks.framework.mvvm.R
import com.mindorks.framework.mvvm.data.api.ApiHelper
import com.mindorks.framework.mvvm.data.api.ApiServiceImpl
import com.mindorks.framework.mvvm.data.model.User
import com.mindorks.framework.mvvm.ui.base.ViewModelFactory
import com.mindorks.framework.mvvm.ui.main.adapter.MainAdapter
import com.mindorks.framework.mvvm.ui.main.viewmodel.MainViewModel
import com.mindorks.framework.mvvm.utils.Status
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private lateinit var mainViewModel: MainViewModel
    private lateinit var adapter: MainAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupUI()
        setupViewModel()
        setupObserver()
    }

    private fun setupUI() {
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = MainAdapter(arrayListOf())
        recyclerView.addItemDecoration(
            DividerItemDecoration(
                recyclerView.context,
                (recyclerView.layoutManager as LinearLayoutManager).orientation
            )
        )
        recyclerView.adapter = adapter
    }

    private fun setupObserver() {
        mainViewModel.getUsers().observe(this, Observer {
            when (it.status) {
                Status.SUCCESS -> {
                    progressBar.visibility = View.GONE
                    it.data?.let { users -> renderList(users) }
                    recyclerView.visibility = View.VISIBLE
                }
                Status.LOADING -> {
                    progressBar.visibility = View.VISIBLE
                    recyclerView.visibility = View.GONE
                }
                Status.ERROR -> {
                    //Handle Error
                    progressBar.visibility = View.GONE
                    Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
                }
            }
        })
    }

    private fun renderList(users: List<User>) {
        adapter.addData(users)
        adapter.notifyDataSetChanged()
    }

    private fun setupViewModel() {
        mainViewModel = ViewModelProviders.of(
            this,
            ViewModelFactory(ApiHelper(ApiServiceImpl()))
        ).get(MainViewModel::class.java)
    }
}

Finally, add the Internet Permission in your project. Add the following in the AndroidManifest.xml:

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

Now, build the project and run the app on the device. It should load the data into the UI.

Project Source Code and What Next?

You can find the complete project here.

As we have done some simplifications in this project for the Beginners level, so, we can improve this project to go to the Advanced level, a few of the things which we can improve are as follows:

  • Implement Dependency Inject Framework - Dagger in the project.
  • Make ApiService Singleton and reuse the same instance for all the features.
  • Create base classes such as BaseActivity.
  • Handle all the API errors at a single place in a better way.
  • Create Interfaces for the classes wherever required.
  • You can learn Kotlin Coroutines from here step by step and migrate to Coroutines.
  • Take advantage of Android KTX - Kotlin Extensions.
  • Write Unit-Test
  • and so on.

If you are preparing for your next Android Interview, Join our Android Professional Course to learn the latest in Android and land job at top tech companies.

Show your love by sharing this blog with your fellow developers.

Click here to share on Twitter.

Also, Let’s become friends on Twitter, Linkedin, Github, Quora, and Facebook.