Learning should always be fun

Working with RecyclerView in Android with Kotlin

Working with RecyclerView in Android with Kotlin

Hello gud ppl! In this tutorial we will work on android RecyclerView with Kotlin . This tutorial will walk through the basic of android RecyclerView. So what is a RecyclerView? Well RecyclerView is a ViewGroup. So what is ViewGroup? Well ViewGroup is a View 🙂 . So what is View? Now this is the point where everything gets interesting. View is the building block of android UI components. Hence RecyclerView is also one of the UI component that helps to display scrolling list for large data sets. In simple words RecyclerView is the advance form of ListView.

You will be able to find many tutorials of RecyclerView online. In this tutorial we will see different challenges you may face while working with RecyclerView. Before going further don’t hesitate to read the official RecyclerView docs here.

 

Before we begin

 

There are four things to remember while working with RecyclerView (according to me :))

  1. Adapter
  2. LayoutManager
  3. ViewHolder
  4. onBindViewHolder function

If you are just getting started with android then these things will look very terrifying at first. But trust me it only takes some few practices to get used to it. I will try to explain as simple as possible.

 

Challenges while working with list

 

I have mentioned above that RecyclerView is a UI component that extends ViewGroup. Now for UI component you must feed something to display. For RecyclerView we feed list items. So how are you going to feed these items to the list? In order to feed items to the list we must be able to tackle the following challenges:

  1. How are these list going to be displayed ( In horizontal format or vertical format or Grid etc… ) ?
  2. What is the UI / design of each list item?
  3. How long will the list be and how to update them?

In order to solve this problem we have the four important things that we listed above.

  • The LayoutManager helps to determine how the list is going to be displayed i.e horizontal, vertical or grid.
  • We define the UI of a list item from ViewHolder.
  • onBindViewHolder will be called when ever an item is displayable in the scroll list. ViewHolder and position of the item is passed when it is called. Hence from this information we can update and display the item.
  • Adapter helps to handle the list of items i.e when added, when deleted, when updated, the number of items available etc…

 

Let’s get started …

 

Lets create a new android project with kotlin support.

On MainActivity.kt write the following code:

 

package info.techdai.recyclerview

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView // The recycler view UI component
    private lateinit var viewAdapter: RecyclerView.Adapter<*> // Adapter for the recyclerview
    private lateinit var viewManager: RecyclerView.LayoutManager // the layout manager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // we are creating the LinearLayoutManager here
        viewManager = LinearLayoutManager(this)
        viewAdapter = MyAdapter(prepareDataSet())

        recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            setHasFixedSize(true)

            // use a linear layout manager
            layoutManager = viewManager

            // specify an viewAdapter (see also next example)
            adapter = viewAdapter

        }

    }

    /**
     * Create a list of items
     * in our case its the MyModel object list
     */
    fun prepareDataSet():ArrayList<MyModel>{
        val list = ArrayList<MyModel>()


        for (i in 1..300) {
            list.add(MyModel("item"+i,false))
        }

        return  list

    }
}

 

 

Create new kotlin file called MyModel.kt and write the following code :

 

package info.techdai.recyclerview

/**
 * Created by abhijet on 4/30/19.
 */


class MyModel(var text: String, var isChecked: Boolean) {

    /**
     * Update my model data
     */
    fun update(text: String,isChecked: Boolean){
        this.text= text
        this.isChecked = isChecked
    }
}

 

Under activity_main.xml write the following :

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.techdai.recyclerview.MainActivity">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</android.support.constraint.ConstraintLayout>

 

On MainActivity.kt we have assigned activity_main.xml layout to the main activity view. It has one RecyclerView which will hold the items that we are displaying. We have created a list of items from prepareDataSet() function. This function will genereate 300 MyModel object list. Then we assign this list to the adapter.

Now lets see the adapter code.

 

Create MyAdapter.kt with the following code :

 

package info.techdai.recyclerview

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.TextView

/**
 * Created by abhijet on 4/30/19.
 */
class MyAdapter(private val myDataset: ArrayList<MyModel>) :
        RecyclerView.Adapter<MyAdapter.MyViewHolder>() {


    /**
     * We are creating our view holder on this function
     * @return
     * MyViewHolder object
     */
    override fun onCreateViewHolder(parent: ViewGroup,
                                    viewType: Int): MyAdapter.MyViewHolder {
        /**
         * Lets firts create a layout for a  single item
         */
        val linearLayout = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_layout, parent, false) as LinearLayout
        // set the view's size, margins, paddings and layout parameters
        return MyViewHolder(linearLayout)  // return the view holder
    }

    /**
     *This function is invoked by layout manager when an item is visible or binded
     * Use this function to make changes to the item specific views
     */
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.itemText.text = myDataset[position].text
    }

    /**
     * This function returns the size of the list holding by our adapter
     * It is very important that we return the actual size of the list here
     * IF zero is returned by this function no item will be displayed in the recyclerview
     */
    override fun getItemCount() = myDataset.size


    /**
     * MyViewHolder
     * ------------
     *
     * MyViewHolder is our view holder
     * Every Layout of each item will be defined here
     *
     * We currently have a TextView and CheckBox in each item
     */
    class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view){
        lateinit var itemText: TextView
        lateinit var checkBox: CheckBox

        init {
            itemText = view.findViewById(R.id.item_text) as TextView
            checkBox = view.findViewById(R.id.checkbox) as CheckBox
        }
    }
}

 

Finally create a layout file named item_layout.xml with the following code :

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp">

    <TextView
        android:id="@+id/item_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="16dp"
        android:text="test"
        android:layout_weight="5"/>

    <CheckBox
        android:id="@+id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>
</LinearLayout>

 

Now this is where everything gets interesting. onCreateViewHolder is called when new item is to be presented in the RecyclerView. This is where we declare our ViewHolder. Our each item will have a view from item_layout.xml file. Hence we inflate a view from this file and pass it to the ViewHolder.

The RecyclerView will know how many items to display with the help of getItemCount() function. This function should always return the number of items in the list. Lets say you return zero from this function then no matter how long your list is you will always see RecyclerView empty.

onBindViewHolder is called when a view is to be displayed in the specific position.

You shoud update the item view here i.e your item from view holder should be update and changed at onBindViewHolder function.

 

Try to run the code and you will see 300 items list in the screen. Try to check some of them and try to scroll. WTF!!! The recyclerView shows item checked in random.

 

What is wrong with RecyclerView?

 

There is nothing wrong with RecyclerView. Instead you must be happy that its working the way it should be!! The RecyclerView tries to recycle each view when they are being reused constantly. So when you are checking a checkbox in some view, the view will be reused in other items when they are being displayed. This is why you will see random checks while you scroll. So whats the solution?

 

Always update view in onBindViewHolder function

 

We have already discussed about onBindViewHolder function. We must always update our views here. In our case we should check if the specific item has checked set to true or not. Hence lets update the onBindViewHolder function as :

 

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {

        val model = myDataset[position]
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.itemText.text = model.text
        // set the checked status
        holder.checkBox.isChecked = model.isChecked

        holder.checkBox.setOnClickListener() { view ->
            val checkBox = view as CheckBox
            model.isChecked = checkBox.isChecked
            myDataset[position] = model
            notifyItemChanged(position)
        }

    }

 

Here we have set the checkbox status in the onBindViewHolder function. We have also updated our model by listening to check status of the checkbox via setOnClickListener function. Finally we need to tell our Adapter that some changes has been done to a specific position using notifyItemChanged function. Now try to run the code. You will now observe that the checkbox behave very well as expected.

 

Few things to think in here.

  • Why did we use notifyItemChanged? There were other options for notifying Adapter like notifyDataSetChanged.
  • We could have use setOnCheckedChangeListener to check the checkbox status. But we used here click listener. Why  ?

I will leave this two question for you guys to research. For now our RecyclerView works as perfectly as we expected. I hope you like this tutorial. Cheers!!

 

Feature Image Credit :

Photo by Tinh Khuong on Unsplash