How to Create Android Universal Recycler View Adapter with MVVM And Data Binding

Hello friends, In android application development the most repetitive task is to setup recycler view, in this post, we will discuss how we can create a universal recycler view adapter for most of the cases with the help of MVVM and data binding.

Enable Data Binding

Add below code in app level build.gradle inside android{ }

dataBinding {
        enabled = true
    }

Add Dependencies

First dependency for MVVM components like ViewModel, etc.

implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'

Create a recycler view item (item_simple.xml)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="model"
            type="com.techpaliyal.androidkotlinmvvm.model.BasicModel" />
        <variable
            name="listener"
            type="com.techpaliyal.androidkotlinmvvm.listeners.BasicListener" />
    </data>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:padding="5dp"
        android:textSize="16sp"
        android:text="@{model.name}"
        android:onClick="@{()->listener.onClick(model)}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Note: data variable’s names should be model and listener for all item layouts use the Universal adapter

Code for the Universal Recycler View Adapter

class UniversalRecyclerAdapter<T>(
    @LayoutRes val resource: Int, var data: ArrayList<T>,
    val listener: Any? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    fun updateData(data: ArrayList<T>){
        this.data = data
        notifyDataSetChanged()
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflator = LayoutInflater.from(parent.context)
        val binding =
            DataBindingUtil.inflate<ViewDataBinding>(layoutInflator, resource, parent, false)
        return MyViewHolder(binding)
    }
    override fun getItemCount(): Int {
        return data.size
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is UniversalRecyclerAdapter<*>.MyViewHolder) {
            val item = data.get(position)
            if (item != null)
                holder.setupData(item)
        }
    }
    inner class MyViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
        fun setupData(model: Any) {
            binding.setVariable(BR.model, model)
            binding.setVariable(BR.listener, listener)
        }
    }
}

the <T> is used to define the model, it is a dynamic type.

BR is a directory file which maintains the variable reference of data binding, it is the same as R file in android.

In item_simple.xml the variable name should be model and listener so detected by Universal Recycler Adapter.

Create a binding adapter

@BindingAdapter(value = ["tools:data","tools:itemList", "tools:itemListener"], requireAll = false)
fun <T> setAdapter(recyclerView: RecyclerView, data : MutableLiveData<ArrayList<T>>, @LayoutRes listItem : Int = R.layout.item_simple, itemListener: Any){
    if (recyclerView.adapter == null){
        recyclerView.adapter =  UniversalRecyclerAdapter(listItem, data.value ?: ArrayList(), itemListener)
    }else{
        if (recyclerView.adapter is UniversalRecyclerAdapter<*>) {
            val items = data.value ?: ArrayList()
            (recyclerView.adapter as UniversalRecyclerAdapter<T>).updateData(items)
        }
    }
}

note: This code is directly inside RecyclerViewExtension.kt

This above data binding function helps to set an adapter, data (array) and item layout directly from XML, so no need to do from code.

activity_basic_listing.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <data>
        <variable
            name="viewModel"
            type="com.techpaliyal.androidkotlinmvvm.ui.view_model.BasicListingActivityViewModel" />
    </data>
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.BasicListingActivity">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:itemList="@{@layout/item_simple}"
        tools:itemCount="5"
        tools:data="@{viewModel.data}"
        tools:itemListener="@{viewModel.basicListener}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

here we use attributes
tools:itemList=”@{@layout/item_simple}”
tools:data=”@{viewModel.data}”
tools:itemListener=”@{viewModel.basicListener}”

which are declared in DataBinding function

Create BasicListingActivityViewModel

class BasicListingActivityViewModel(application: Application) : AndroidViewModel(application),
    BasicListener<BasicModel> {
    override fun onClick(model: BasicModel) {
        Toast.makeText(getApplication(), model.name, Toast.LENGTH_LONG).show()
    }
    val data = MutableLiveData<ArrayList<BasicModel>>(ArrayList<BasicModel>())
    val basicListener : BasicListener<BasicModel> ?= this
    fun setData(){
        val tempArr = ArrayList<BasicModel>()
        tempArr.add(BasicModel(name = "Yogesh"))
        tempArr.add(BasicModel(name = "Umesh"))
        tempArr.add(BasicModel(name = "Sohan"))
        tempArr.add(BasicModel(name = "Madan"))
        data.value = tempArr
    }
}

ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment.

In this ViewModel, we took 2 variables data and basicListener (to listen from recycler item)

Connect the view model with BasicListingActivity

class BasicListingActivity : AppCompatActivity(){
    lateinit var binding: ActivityBasicListingBinding
    companion object{
        fun start(context: Context){
            val intent = Intent(context, BasicListingActivity::class.java)
            context.startActivity(intent)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_basic_listing)
        binding.lifecycleOwner = this
        val viewModel = ViewModelProviders.of(this).get(BasicListingActivityViewModel::class.java)
        binding.viewModel = viewModel
        binding.executePendingBindings()
        binding.viewModel?.setData()
    }
}

Now run our application.

Output :

Basic Listing Universal Recycler View

Any design can be achieved using this here is a some sample

You can check full code on Github

If found any bug or having suggestions, please comment below.

Leave a Reply