Easy Android Programming: MVVM

5 min read

Android Development

Prologue

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

Code structure for Kotlin WeatherData

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.firebasedatabasedemo

import `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

Output Screen 1 for kotlinweatherdata

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.viewmodels

import `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 an end of blog. Share your feedback.

Thanks for reading

Happy Coding

Leave a Reply

Your email address will not be published. Required fields are marked *