Implementing a Nested RecyclerView in Android with ConcatAdapter

MergeAdapter helps combine adapters sequentially to be displayed in single RecyclerView.

With the UI of the applications getting increasingly complex, there will be a time where you need to deal with RecyclerViews inside another RecyclerView. A good example of this kind of UI would be the Klook application.

the Klook application screenshot


It is likely that they made this UI using a nested RecyclerView. One way to make this happen is to structure RecyclerView like this.

Nested RecyclerView structure without MergeAdapter.


This how I would create the Outer Adapter for controlling the content of the nested RecyclerView. Each Viewholder contains header and list. The rough idea of the code of the Outer Adapter may look something like this:


class OutAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheckUpResultViewHolder {
        return when (viewType) {
            R.layout.layout_header -> HeaderViewHolder(parent)
            R.layout.layout_first_section -> FirstSectionViewHolder(parent)
            R.layout.layout_second_section -> SecondSectionViewHolder(parent)
            R.layout.layout_third_section -> ThirdSectionViewHolder(parent)
        }
    }

    override fun getItemCount(): Int {
        return 6
    }

    override fun onBindViewHolder(holder: CheckUpResultViewHolder, position: Int) {
        //binding data
    }

    override fun getItemViewType(position: Int): Int = when (position) {
        0, 2, 4 -> R.layout.layout_header
        1 -> R.layout.layout_first_section
        3 -> R.layout.layout_second_section
        5 -> R.layout.layout_third_section
    }
}
//kotlin-language

Update: Google has changed the name from MergeAdapter to ConcatAdapter.

Now let's talk about the MergeAdapter. MergeAdapter is the class that available since recyclerview:1.2.0-alpha02.
It helps combine adapters sequentially for display in a RecyclerView without creating an extra RecyclerView adapter. In our case, if we bring in the MergeAdapter, we no longer need to create the Outer Adapter. We can structure our nested RecyclerView like this.

Nested RecyclerView structure with MergeAdapter


As you can see, we separate each section of the UI into different adapters. Using a MergeAdapter helps us separate the logic of each section into one adapter, and we can reuse the adapter easily. Then we can combine each adapter like the snippet below:

val mergeAdapter = MergeAdapter(
    firstHeaderAdapter, 
    firstAdapter, 
    secondHeaderAdapter,
    secondAdapter,
    thirdHeaderAdapter,
    thirdAdapter
)
recyclerView.adapter = mergeAdapter

The three header adapters can be different instances of the same Header adapter class. Inside the Header adapter, the getItemCount is always one, and we can use View Type to control the state of the header layout. Apart from the header adapter, the rest will be the same as a typical RecyclerView Adapter

How does MergeAdapter make our life easier?

  1. No need for an extra Adapter to the outermost RecyclerView.
  2. With the MergeAdapter, you can easily encapsulate your adapters rather than combine data sources and logic into one adapter. To give you a clearer explanation, let's look at the next scenario. Suppose that the header of each section in the nested Recyclerview needs to be able to show the loading state.

The scenario where the header can have multiple states.


Without MergeAdapter, the showing and hiding the loading state logic needs to be in the Outer Adapter. Having the header logic and the list logic inside the same adapter is not really a best practice because we might not be able to reuse the logic elsewhere. Therefore we are better separating each section into its own adapter. With the MergeAdapter, we can easily combine each section into the single RecyclerView. Each adapter encapsulates its logic which makes them reusable. 

Additional Note

  • If multiple adapters inside the MergeAdapter need to re-use the same ViewHolder class, there is a little more config to be made. We need to set the isolateViewTypes to false.

val builder = MergeAdapter.Config.Builder()
builder.setIsolateViewTypes(false)

val mergeAdapter = MergeAdapter(
    builder.build(),
    firstHeaderAdapter, 
    firstAdapter, 
    secondHeaderAdapter,
    secondAdapter,
    thirdHeaderAdapter,
    thirdAdapter
)

  • The adapters inside the MergeAdapter can be added or removed dynamically like this.
mergeAdapter.addAdapter(6,fourtAdapter)

mergeAdapter.removeAdapter(//adapter)

  • Avoid calling notifyDataSetChanged in the inner adapters since the MergeAdapter will also call notifyDataSetChanged. Therefore, using notifyItemRangeChanged, notifyItemInserted, notifyItemChange, and others are preferable.
Like 14 likes
Ben Kitpitak
Mobile Developer at OOZOU in Bangkok, Thailand
Share:

Join the conversation

This will be shown public
All comments are moderated

Comments

Raghunandan
April 25th, 2021
Is it possible to paginate certain adapter in merge/concat adapter

Brenton Unger
June 24th, 2021
Everything I've seen about Concat adapters says you cannot control the inner layouts. So the example you have given with horizontal layouts on the nested scrollview cannot be done. If so, please point out some resources for this as I need it for my work.

Ben
June 24th, 2021
@ Raghunandan
It is possible. However, when I try with the google paging lib 2 it doesn't work. So you need to implement your own listener to catch when the recyclerview reach the end.

Brenton Unger
June 24th, 2021
@Brenton Unger
It can be done with a little more steps. The `first adapter` I mentioned in the example will be an adapter with only one `viewholder`. The `viewholder` then will hold another `RecyclerView` which need to set up with another adapter. Then you can set the `layoutManager` to the inner `RecyclerView` to horizontal.

Get our stories delivered

From us to your inbox weekly.