Easy Android Programming: MVVM

Prologue

success_anil
The Code Monster
Published in
6 min readFeb 1, 2020

--

This is first tutorial in EASY Android Programming: MVVM series.

Eminent Mr. Woody Guthrie has said it right

Any fool can make something complicated. It takes a genius to make it simple.

So how all this is connected?

Most of the experienced or intermediate software developers know that its the quality of the application that makes it saleable. Users like that app which doesn’t crash and do the desired work in lesser no of steps.

UI Experience designers put a lot of midnight oil to come up with eye-catching UI, yet, easy to navigate. The UI may allow a user to perform a task with a lesser no of steps.

Hardworking developers put a lot of effort to develop an application that confirms UX design by using required design patterns in coding.

Process

In this article, I will make an android application as a metaphor but the rules are similar for any type of software development. To make things clearer, I would start with MVVM in Android development.

Before proceeding further, I would expect you know Git (SCM), if not please study one of the earlier articles this and this.

Checkout the weatherdata branch for the repository link . if you see code structure

In continuation of the above, there are a bunch of kotlin files in firebasedatabasedemo package. Lets open FrontListFragment.kt file you will find below code.

/*
* Copyright (c) 2019. Relsell Global
*/
package `in`.relsellglobal.firebasedatabasedemoimport `in`.relsellglobal.firebasedatabasedemo.pojo.CityContent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*
/**
* A fragment representing a list of Items.
* Activities containing this fragment MUST implement the
* [FrontListFragment.OnListFragmentInteractionListener] interface.
*/
class FrontListFragment : Fragment() {
// TODO: Customize parameters
private var columnCount = 1
private var recyclerView:RecyclerView? = null
private var myItemRecyclerViewAdapter : MyItemRecyclerViewAdapter? = null val cityContentList: MutableList<CityContent> = ArrayList() val APPID = BuildConfig.OPENWEATHERDATA_API_KEY override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
recyclerView = view.findViewById(R.id.list);
// Set the adapter
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var city = CityContent()
city.cityName = "dehradun"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city); city = CityContent()
city.cityName = "new delhi"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "hyderabad"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "chennai"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "mumbai"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "mangalore"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "dispur"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
city = CityContent()
city.cityName = "indore"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
cityContentList.add(city);
recyclerView!!.layoutManager = LinearLayoutManager(activity);
myItemRecyclerViewAdapter = MyItemRecyclerViewAdapter(cityContentList,activity)
recyclerView!!.adapter = myItemRecyclerViewAdapter
} override fun onDetach() {
super.onDetach()
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson
* [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
companion object { // TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
@JvmStatic
fun newInstance(columnCount: Int) =
FrontListFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}

app level build.gradle file

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'def localProperitiesFile = rootProject.file("local.properties")
def localProperties = new Properties()
localProperties.load(new FileInputStream(localProperitiesFile))
android {
compileSdkVersion 28
defaultConfig {
applicationId "in.relsellglobal.firebasedatabasedemo"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "OPENWEATHERDATA_API_KEY", "\""+localProperties['openweather_data_api_key']+"\""
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.firebase:firebase-core:17.2.2'
implementation 'com.google.firebase:firebase-database:19.2.0'
implementation 'com.android.volley:volley:1.1.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
apply plugin: 'com.google.gms.google-services'

When you run the application you see below screen

Right now we are having the list of cities as in-memory ArrayList. So it’s faster to load. Even rotation changes will not crash the application. But what if we load our list from a remote database like firebase realtime db.

Problem

So the above process to download the list from firebase might take few milliseconds and by that time user may choose to rotate the screen of the device.

Consequently, in android, the corresponding activity would restart on rotation now if we have designed the architecture of our application as MVP (Model view presenter ), so our view and the model classes would be interacting on the bases of interfaces or callbacks.

Basically, this means that Model classes have reference to view in direct or wrapped form. so that, whenever data arrives after a network call, the model may update the view.

On rotation, the activity would restart all the view become null and the corresponding earlier view’s reference in model class becomes null, and once this happens if model classes try to update a null view app crashes. As a side note, customers don’t like those apps which crash. It hardly matters for them, how much pain the developer has taken to develop the application.

The Rescue

Rescue is introducing MVVM Pattern to your code.

MVVM Pattern involves Model View ViewModel. Now in simple terms Model updates ViewModel. The view has put observer on ViewModel so when view requires only then it can retrieve updated values from ViewModel.

Therefore the crash that may result when the device is rotated won’t appear. Moreover, a ViewModel has no information regarding activity or Fragments.

So in our application, the code for FrontListFragment.kt onActivityCreated method would become

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
recyclerView!!.layoutManager = LinearLayoutManager(activity); val model = ViewModelProviders.of(this).get(CitiesViewModel::class.java)
model.getCitiesList().observe(this, androidx.lifecycle.Observer { cityContentList ->
myItemRecyclerViewAdapter = MyItemRecyclerViewAdapter(cityContentList,activity)
recyclerView!!.adapter = myItemRecyclerViewAdapter
})
}

In the above code snippet, you will see fragment is observing on data from ViewModel. Now since we have introduced ViewModel our code structure will be like below

We have added a new package called ViewModel which will keep our ViewModel classes.

We have added CitiesViewModel class.

package `in`.relsellglobal.firebasedatabasedemo.viewmodelsimport `in`.relsellglobal.firebasedatabasedemo.BuildConfig
import `in`.relsellglobal.firebasedatabasedemo.pojo.CityContent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CitiesViewModel : ViewModel() {
val APPID =
BuildConfig.OPENWEATHERDATA_API_KEY
private lateinit var citiesContent: MutableLiveData<List<CityContent>> fun getCitiesList(): LiveData<List<CityContent>> {
if (!::citiesContent.isInitialized) {
citiesContent = MutableLiveData()
loadCities()
}
return citiesContent
}
private fun loadCities() {
val listOfCities = mutableListOf<CityContent>()
var city = CityContent()
city.cityName = "dehradun"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "new delhi"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "hyderabad"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "chennai"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "mumbai"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "mangalore"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "dispur"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
city = CityContent()
city.cityName = "indore"
city.apiUrl = "http://api.openweathermap.org/data/2.5/weather?q="+city.cityName+"&appid="+APPID
listOfCities.add(city)
citiesContent.value = listOfCities }
}

To keep code simple we will stop here , we are not exploring other components in MVVM and fetching data from other sources.

By introducing MVVM to the code, we have touched two main advantages of MVVM

  1. Clean Separation of Concerns
  2. ViewModels are Lifecycle aware.

Conclusion

This brings us to the end of the blog. Share your feedback.

Thanks for reading

Happy Coding

Article originally published at the blog

--

--

success_anil
The Code Monster

Learning via repetition. Long but sturdy, reliable way to learn.