RSS Reading MVVM based improvement

3 min read

Android Development

Prologue

For me, MVVM is one of the best design patterns available today for improving RSS Reading in kotlin via MVVM based code.

In our last blog, under the EASY ANDROID Programming Category, you helped yourself by developing a basic RSS Reader Application in Kotlin.

To start with, I would rephrase or rather a popular phrase,

Practise makes a man perfect.

So we practice a lot, improve our existing code. Great.

But why?

There is one another saying popular among army people,

soldiers who shed a lot of sweat by hard work in practice, will shed less blood in battles.

Enough of motivation :). Let’s get down to work

Introduction to Jetpack

If you pay attention to the below image, you will see Google developers have introduced Jetpack in Android to supercharge your app development.

RSS Reading
Supercharge Android development

In the context of the topic for this blog, we are going to interact with the Architecture component of Android Jetpack. We will be improving existing code for RSS Reading Application made using Kotlin language. In addition to this, I would like to suggest the reader may have knowledge of git.

File Organization

Below is a snapshot of files in our code at Github. Please checkout the master branch.

RSS Reading : initial code structure

Conceptually, the Architecture component is implemented as MVVM. See below image

RSS Reading  Androd Jetpack Architecture component

The data flow between the user interface layer, view model layer and singleton repository layer. Knowingly I am writing a singleton repository so that it is easier to remember that there should be only a single instance of the repository within the application.

Let’s add two folders named repository and viewmodels.

RSS Reading : code structure after adding two folders

if you are new to java or kotlin, I would like to tell you about singleton classes. This class has only a single instance throughout the application. Singleton class is very important and follows an important design pattern called Singleton.

To create a singleton class, we will make it’s constructor private so its objects can’t be created using new. But in our application, we need at least one instance of the singleton class, so there must be some way of creating an instance of the singleton class. Here comes the initializer method which when called from multiple places in code will return a new instance of a singleton class for the very first time but for another time it will return the same instance. Below code in kotlin language is written like that. Please add a file called RssRepository.kt inside the repository folder.

package com.relsellglobal.kotlinrssreading.repository

class RssRepository private constructor(){

    private object HOLDER {
        val INSTANCE = RssRepository()
    }

    companion object {
        val instance: RssRepository by lazy { HOLDER.INSTANCE }
    }
}

Connecting ViewModel class with Repository

Here is our updated RssFragmentViewModel class or ViewModel layer class

/*
 * Copyright (c) 2020. Relsell Global
 */

package com.relsellglobal.kotlinrssreading.viewmodels

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.relsellglobal.kotlinrssreading.RssItem
import com.relsellglobal.kotlinrssreading.repository.RssRepository

class RssFragmentViewModel : ViewModel() {
    private lateinit var rssItems : MutableLiveData<List<RssItem>>


    fun fetchRssItems():LiveData<List<RssItem>> {
        if(!::rssItems.isInitialized) {
            rssItems = MutableLiveData()
            rssItems = loadRssItems()
        }
        return rssItems
    }
    private fun loadRssItems() : MutableLiveData<List<RssItem>>{
        rssItems = RssRepository.instance.fetchRSSData()
        return rssItems
    }

}

Updated RssRepository class or repository layer class

/*
 * Copyright (c) 2020. Relsell Global
 */

package com.relsellglobal.kotlinrssreading.repository

import android.os.AsyncTask
import androidx.lifecycle.MutableLiveData
import com.relsellglobal.kotlinrssreading.RssItem
import com.relsellglobal.kotlinrssreading.RssParser
import java.io.IOException
import java.io.InputStream
import java.net.URL
import javax.net.ssl.HttpsURLConnection

class RssRepository private constructor(){

    private val RSS_FEED_LINK = "https://www.relsellglobal.in/feed/";

    private object HOLDER {
        val INSTANCE = RssRepository()
    }

    companion object {
        val instance: RssRepository by lazy { HOLDER.INSTANCE }
    }

    fun fetchRSSData():MutableLiveData<List<RssItem>> {
        val url = URL(RSS_FEED_LINK)
        val data = MutableLiveData<List<RssItem>>()
        RssFeedFetcher(data).execute(url)
        return data
    }

    class RssFeedFetcher(data: MutableLiveData<List<RssItem>>) : AsyncTask<URL, Void, List<RssItem>>() {
        private var stream: InputStream? = null;
        val dataObj:MutableLiveData<List<RssItem>> = data
        override fun doInBackground(vararg params: URL?): List<RssItem>? {
            val connect = params[0]?.openConnection() as HttpsURLConnection
            connect.readTimeout = 8000
            connect.connectTimeout = 8000
            connect.requestMethod = "GET"
            connect.connect();

            val responseCode: Int = connect.responseCode;
            var rssItems: List<RssItem>? = null
            if (responseCode == 200) {
                stream = connect.inputStream;


                try {
                    val parser = RssParser()
                    rssItems = parser.parse(stream!!)

                } catch (e: IOException) {
                    e.printStackTrace()
                }


            }

            return rssItems

        }

        override fun onPostExecute(result: List<RssItem>?) {
            super.onPostExecute(result)
            if (result != null && !result.isEmpty()) {
                dataObj.value = result
            }

        }

    }
}

Now after hooking up with UI Layer, UI Layer class or RssFragment will look like

/*
 * Copyright (c) 2020. Relsell Global
 */

package com.relsellglobal.kotlinrssreading

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.relsellglobal.kotlinrssreading.viewmodels.RssFragmentViewModel


/**
 * A fragment representing a list of Items.
 * Activities containing this fragment MUST implement the
 * [RSSFragment.OnListFragmentInteractionListener] interface.
 */
class RSSFragment : Fragment() {

    // TODO: Customize parameters
    private var columnCount = 1

    private var listener: OnListFragmentInteractionListener? = null

    val RSS_FEED_LINK = "https://www.relsellglobal.in/feed/";

    var adapter: MyItemRecyclerViewAdapter? = null
    var rssItems = ArrayList<RssItem>()

    var listV : RecyclerView ?= null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_item_list, container, false)

        listV = view.findViewById(R.id.listV)
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        adapter = MyItemRecyclerViewAdapter(rssItems, listener,activity)
        listV?.layoutManager = LinearLayoutManager(activity,LinearLayoutManager.VERTICAL,false)
        listV?.adapter = adapter
        
        val model = ViewModelProviders.of(this).get(RssFragmentViewModel::class.java)

        model.fetchRssItems().observe(this, Observer {
            if (it != null && !it.isEmpty()) {
                rssItems.addAll(it)
                adapter?.notifyDataSetChanged()
            }
        })
        

    }
    

    interface OnListFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onListFragmentInteraction(item: RssItem?)
    }

}

Hence the output

output

This brings us to the end of blog. We can imporve it more by introducing retrofit (a networking lib) for fetching data for rss items. But that we will be covering in other blog.

Thanks for your time and for reading this.

If you like it share it, if not feel free to let me know what I can improve, please post comments.

Last but not the least code is available at Link under mvvmimprovement branch

Happy coding

Leave a Reply

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