Nested RecyclerView using data binding in Android

Rishabh Deep Singh
5 min readJan 24, 2021

--

As the kotlin.synthetics are deprecated it the now the time to migrate to the Data binding. The following code will help you solve the simple problem of creating nested RecyclerView using DataBinding. I'll be using Kotlin for this project as it's the new standard.

Before starting we need to do some changes in our build.gradle file for the app module. Add the following lines will enable data binding and you can rebuild the project.

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'kotlin-android-extensions'
}
android{
buildFeatures {
dataBinding true
}
}

First, let us drive some of the data classes we need to store data.

dtos/ChildDTO.kt

data class ChildDTO(
var title: String? = null,
var description: String? = null,
var imageURL: String? = null
)

dtos/ParentDTO

data class ParentDTO(
var title: String? = null,
var description: String? = null,
var children: List<ChildDTO>? = null,
)

now that we have created data transfer Objects. it's now time to create a simple dummy data Factory class. where we can fetch some random data to be displayed.

factory/DataFactory.kt

object DataFactory {

// to generate random numbers
private val rand = Random(123123123L)

// Sample titles
private val titles = listOf("Vertigo",
"The Innocents",
"Lawrence of Arabia",
"The Deer Hunter",
"Amadeus",
"Blade Runner",
"Eyes Wide Shut"
)

// Sample descriptions
private val descriptions = listOf(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Pellentesque sagittis odio ut tincidunt scelerisque.",
"Phasellus a neque consequat leo bibendum tempus.",
"Quisque at enim id odio blandit imperdiet nec consequat augue.",
"Cras iaculis lorem a dignissim egestas.",
"Duis quis leo pharetra, vestibulum elit rhoncus, tempus ante.",
"Duis ut lorem aliquet, lobortis massa a, fringilla velit.",
"Fusce non ipsum sed augue gravida ullamcorper."
)

// Returns a sample `ParentDTO` Object
fun getParentList(): List<ParentDTO> {
val list = ArrayList<ParentDTO>()
for (i in 1..rand.nextInt(5, 10)) {
list.add(ParentDTO(
titles[rand.nextInt(titles.size)], descriptions[rand.nextInt(descriptions.size)], getChildList()
))
}
return list
}

// Returns a sample `List<ChildDTO>` Object to populate the parent object
private fun getChildList(): List<ChildDTO> {
val list = ArrayList<ChildDTO>()
for (i in 1..rand.nextInt(5, 10)) {
list.add(getRandomChild())
}
return list
}

// Returns a sample `ChildDTO` Object
private fun getRandomChild(): ChildDTO {
return ChildDTO(
titles[rand.nextInt(titles.size)],
descriptions[rand.nextInt(descriptions.size)],
"@mipmap/img_sample"
)
}
}

Now let us define how our child Object is to be displayed the following image describes it and we will be creating the layout for it. Also, we will be defining layout as well as a variable for the same to display data.

Child View

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

<import type="android.view.View" />

<variable
name="data"
type="com.rishabh.nestedrecyclerview.dtos.ChildDTO" />
</data>

<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="4dp"
android:elevation="4dp">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:background="@color/design_default_color_secondary"
android:paddingHorizontal="8dp"
android:paddingVertical="8dp">

<ImageView
android:id="@+id/iv_sub_image"
android:layout_width="46dp"
android:layout_height="46dp"
android:contentDescription="@string/sublist_image"
android:src="@mipmap/img_sample"
android:textColor="@color/black"
android:visibility="@{data.imageURL.isEmpty() ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />

<TextView
android:id="@+id/tv_sub_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@{data.title}"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/iv_sub_image"
app:layout_constraintTop_toTopOf="parent"
tools:text="Sample Child Title" />

<TextView
android:id="@+id/tv_sub_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:lines="2"
android:text="@{data.description}"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_sub_image"
app:layout_constraintTop_toBottomOf="@+id/tv_sub_title"
tools:text="Sample Child Description" />

</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

</layout>

Now that our child's view is ready it's time to create the parent view.

Notice that there are new tags that are there. I'll try to explain them.

  • layout the root where the whole layout is placed.
  • data where we can. define imports as well variables.
  • variable we need to define our variables in the variable tag then only we can use them in our XML. see android:text="@{data.title}" we defined the data as ChildDTO and we can access the properties using data variable.

parent_item.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="data"
type="com.rishabh.nestedrecyclerview.dtos.ParentDTO" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="6dp"
android:paddingHorizontal="8dp"
android:paddingVertical="8dp">

<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="#FF0000"
android:fontFamily="monospace"
android:textStyle="bold"
android:text="@{data.title}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Sample Parent Title" />

<TextView
android:id="@+id/tv_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:text="@{data.description}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
tools:text="Sample Parent Description" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_children"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_description"
tools:listitem="@layout/child_item" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

and finally our activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<androidx.constraintlayout.widget.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"
android:paddingHorizontal="8dp"
android:paddingVertical="8dp"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/parent_item" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Now that our XML's are ready it's time to create Adapters for the same.

NOTE: After this, you might have to rebuild the project as the data-binding creates some additional files with LayoutNameBinding example ChildItemBinding for our child_item.xml and similarly for our parent.

Let's start with our ChildAdapter.kt first.

class ChildAdapter(private val childData: List<ChildDTO>?) : RecyclerView.Adapter<ViewHolder>() {

inner class ViewHolder(private val itemView: View, val binding: ChildItemBinding) : RecyclerView.ViewHolder(itemView)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DataBindingUtil.inflate(LayoutInflater.from(parent.context),
R.layout.child_item, parent, false) as ChildItemBinding
return ViewHolder(binding.root, binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if ((childData?.size ?: 0) > position)
holder.binding.data = childData?.get(position)

holder.binding.executePendingBindings()
}

override fun getItemCount() = childData?.size ?: 0
}

and now the ParentAdapter.kt to store the child’s

now we have created a new binding variable of ChildItemBinding type in our ViewHolder and we can use that to bind our data in onBindViewHolder function using holder.binding and the data we set here will be reflected there(in XML).

class ParentAdapter(private val data: List<ParentDTO>) : RecyclerView.Adapter<ViewHolder>() {

inner class ViewHolder(private val itemView: View, val binding: ParentItemBinding) : RecyclerView.ViewHolder(itemView) {}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DataBindingUtil.inflate(LayoutInflater.from(parent.context),
R.layout.parent_item, parent, false) as ParentItemBinding
return ViewHolder(binding.root, binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.data = data[position]
holder.binding.rvChildren.adapter = ChildAdapter(data[position].children)
holder.binding.executePendingBindings()
}

override fun getItemCount() = data.size
}

Similarly here you can see we are setting the data that is passed to the XML using ParentItemBinding created inside the ViewHolder.

and finally MainActivity.kt the root of the views to store the parent.

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val data = DataFactory.getParentList()
binding.rvItems.adapter = ParentAdapter(data)

}
}

Now that every piece is in place we can run our app.

The Github project can be found below.

https://github.com/rishabhdeepsingh/data_binding_nested_recyclerview

Hope you find it informative and useful. If there is anything to discuss comment on it.

--

--