Image for post
Image for post

Easy Android Programming: MVVM

Prologue

Anil Kukreti
Feb 1, 2020 · 6 min read

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

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

Image for post
Image for post

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

Image for post
Image for post

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

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

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

Image for post
Image for post

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

Thanks for reading

Happy Coding

Article originally published at the blog

The Code Monster

Create.Share.Learn

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store