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.
This how I would create the Outer Adapter for controlling the content of the nested RecyclerView. Each Viewholder contains header and list.Theroughidea 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.
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:
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 AdapterHow does MergeAdapter make our life easier?
No need for an extra Adapter to the outermost RecyclerView.
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.
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.
Avoid calling notifyDataSetChanged in the inner adapters since the MergeAdapter will also call notifyDataSetChanged. Therefore, using notifyItemRangeChanged, notifyItemInserted, notifyItemChange, and othersarepreferable.