MVVM Design Pattern in Android

Overview

MVVM is a Model-View-ViewModel design pattern architecture that removes the tight coupling between every application components. In the below image, you can clearly see that every children components don't have a reference to the parent components. They have a reference to an observable though which they can communicate with the parent components.

Components of MVVM

Model

It is the data and business logic of an Android application. It's a data class where you can define the properties and methods too.

View

It is the user interface of an Android application, activities and fragments. These views send the user requests to ViewModel and to get the response, the application components has to subscribe to the observables which the ViewModel has exposed to it.

ViewModel

It is a bridge between the View and the Model in MVVM design pattern. ViewModel isn't aware which view is interacting with it. ViewModel only interacts with the Model and exposes the observable that can be observed by the view.

Steps to follow to integrate with a Room Database

  • Create a model, a data class (table)
 @Entity(tableName = "quote")
data class Quote(
@PrimaryKey(autoGenerate = true)
val id: Int,
val text: String,
val author: String
)
  • Create a Dao
 @Dao
interface QuoteDao {

@Query("SELECT * from quote")
fun getQuotes(): LiveData<List<Quote>>

@Insert
suspend fun insertQuote(quote: Quote)
}
  • Create a Database
 @Database(entities = [Quote::class], version = 1)
abstract class QuoteDatabase : RoomDatabase() {
abstract fun quoteDao(): QuoteDao

companion object {
private var INSTANCE: QuoteDatabase? = null
fun getDatabase(context: Context): QuoteDatabase {
if (INSTANCE == null) {
synchronized(this) {
INSTANCE = Room.databaseBuilder(
context,
QuoteDatabase::class.java,
"quote_database"
)
.createFromAsset("quotes.db")
.build()
}
}
return INSTANCE!!
}
}
}
  • Create a Repository
 class QuoteRepository(private val quoteDao: QuoteDao) {

fun getQuotes(): LiveData<List<Quote>>{
return quoteDao.getQuotes()
}

suspend fun insertQuote(quote: Quote){
quoteDao.insertQuote(quote)
}
}
  • Create a ViewModel
 class MainViewModel(private val quoteRepository: QuoteRepository) : ViewModel() {

fun getQuotes() : LiveData<List<Quote>>{
return quoteRepository.getQuotes()
}

fun insertQuote(quote: Quote){
viewModelScope.launch(Dispatchers.IO){
quoteRepository.insertQuote(quote)
}

}
}
  • Create a factory for ViewModel
 class MainViewModelFactory(private val quoteRepository: QuoteRepository) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(quoteRepository) as T
}
}
  • Bind the data in xml file. To automatically attach DataBinding to the xml 
 <?xml version="1.0" encoding="utf-8"?>
<layout 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">

<data>

<variable
name="quotes"
type="String" />
</data>

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/btnAddQuote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:text="InsertQuote" />

<TextView
android:textSize="32sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="32dp"
android:text="@{quotes}"/>

</LinearLayout>

</ScrollView>
</layout>
  • Create the references of ViewModel in MainActivity
 class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

val dao = QuoteDatabase.getDatabase(applicationContext).quoteDao()
val repository = QuoteRepository(dao)

mainViewModel =
ViewModelProvider(this, MainViewModelFactory(repository)).get(MainViewModel::class.java)

mainViewModel.getQuotes().observe(this, Observer {
binding.quotes = it.toString()
})

binding.btnAddQuote.setOnClickListener {
val quote = Quote(0, "This is testing", "Testing")
mainViewModel.insertQuote(quote)
}
}
}

MVVM with Retrofit and Room Database

  • Dependency
 plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
 dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

// Retrofit
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"

// ViewModel and LiveData
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

// Coroutines
def coroutines_version = "1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

// Room Database
def room_version = "2.4.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
kapt "android.arch.persistence.room:compiler:1.1.1"
implementation "androidx.room:room-ktx:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

}
  • Model
 data class QuoteList(
val count : Int ,
val lastItemIndex : Int ,
val page : Int ,
val results : List<Result> ,
val totalCount : Int ,
val totalPages : Int
)
 @Entity(tableName = "quote")
data class Result(
@PrimaryKey val quoteID : Int ,
val author : String ,
val authorSlug : String ,
val content : String ,
val dateAdded : String ,
val dateModified : String ,
val _id : String ,
val length : Int
)
  • Interface of Retrofit
 interface QuoteService
{

// BASE_URL + "/quotes?page=1

@GET("/quotes")
suspend fun getQuotes(@Query("page") page : Int) : Response<QuoteList>
}
  • Retrofit Helper
 object RetrofitHelper
{
const val BASE_URL = "https://quotable.io/"

fun getInstance() : Retrofit
{
return Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()).build()
}
}
  • Dao for Room Database
 @Dao
interface QuoteDao
{
@Insert
suspend fun addQuotes(quotes : List<Result>)

@Query("SELECT * FROM quote")
suspend fun getQuotes() : List<Result>
}
  • Database class for Room Database
 @Database(entities = [Result::class] , version = 1)
abstract class QuoteDatabase : RoomDatabase()
{
abstract fun quoteDao() : QuoteDao

companion object
{
@Volatile
private var INSTANCE : QuoteDatabase? = null
fun getDatabase(context : Context) : QuoteDatabase
{
if (INSTANCE == null)
{
synchronized(this) {

/* Make sure to pass application context */
INSTANCE = Room.databaseBuilder(context , QuoteDatabase::class.java , "quote_database")
.build()
}
}
return INSTANCE !!
}
}
}
  • Repository for Retrofit and Room Database
 class QuoteRepository(
private val quoteService : QuoteService , private val quoteDatabase : QuoteDatabase
)
{
private val quotesLiveData = MutableLiveData<QuoteList>()
val quotes : LiveData<QuoteList>
get() = quotesLiveData

suspend fun getQuotes(page : Int)
{
val result = quoteService.getQuotes(page)
if (result.body() != null)
{
quoteDatabase.quoteDao().addQuotes(result.body() !!.results)
quotesLiveData.postValue(result.body())
}
}
}
  • ViewModel for MainActivity
 class MainViewModel(private val quoteRepository : QuoteRepository) : ViewModel()
{
init
{
viewModelScope.launch(Dispatchers.IO) {
quoteRepository.getQuotes(1)
}
}

val quotes : LiveData<QuoteList>
get() = quoteRepository.quotes
}
  • ViewModelFactory for MainActivity
 class MainViewModelFactory(private val quoteRepository : QuoteRepository) :
ViewModelProvider.Factory
{
override fun <T : ViewModel> create(modelClass : Class<T>) : T
{
return MainViewModel(quoteRepository) as T
}
}
  • Application class for MainActivity
 class QuoteApplication : Application()
{
lateinit var quoteRepository : QuoteRepository

override fun onCreate()
{
super.onCreate()
initialize()
}

private fun initialize()
{
val quoteService : QuoteService =
RetrofitHelper.getInstance().create(QuoteService::class.java)
val quoteDatabase = QuoteDatabase.getDatabase(this)
quoteRepository = QuoteRepository(quoteService , quoteDatabase)
}
}
  • Implementation in MainActivity
 class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "MainActivity"
}

private lateinit var binding : ActivityMainBinding
private lateinit var mainViewModel : MainViewModel
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this , R.layout.activity_main)
val repository = (application as QuoteApplication).quoteRepository
mainViewModel = ViewModelProvider(
this , MainViewModelFactory(repository)
)[MainViewModel::class.java]
mainViewModel.quotes.observe(this , Observer {
Log.d(TAG , it.results.toString())
})
}
}
  • Update AndroidManifest.xml file to use Application class
 <application
android:name=".QuoteApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MVVM_Retrofit_Android">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

Reference



Comments

Popular posts from this blog

SSLSocketFactory in Android

Architecture Components in Android

DataBinding in Android