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.
It is likely that they made this UI using a nested RecyclerView. One way to make this happen is to structure RecyclerView like this. In this example, we will demonstrate how to set up the nested RecyclerView.
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.
Introduction to ConcatAdapter in Android
ConcatAdapter is a powerful tool in Android that enables developers to combine multiple adapters into a single adapter, making it easier to manage complex UI layouts. This feature is particularly useful when dealing with RecyclerViews, as it allows for better encapsulation of adapters and reusability. By using ConcatAdapter, developers can focus on designing the UI and user experience, rather than worrying about the underlying implementation details. This approach not only simplifies the development process but also enhances the maintainability of the codebase.
Understanding the Adapter
An adapter is a crucial component in Android that acts as a bridge between the data and the UI. It is responsible for providing the data to the RecyclerView and binding it to the views. In the context of ConcatAdapter, an adapter can be thought of as a single unit of data that needs to be displayed in the RecyclerView. By combining multiple adapters, ConcatAdapter enables developers to create complex UI layouts with ease. Each adapter handles a specific type of data, ensuring that the logic is modular and easier to manage. This separation of concerns is key to building scalable and maintainable applications.
Setting Up ConcatAdapter
Setting up ConcatAdapter is a straightforward process. First, you need to create multiple adapters that will be combined into a single adapter. Each adapter should have its own data source and layout. Once you have created the adapters, you can combine them using the ConcatAdapter class. You can then set the ConcatAdapter to the RecyclerView, and it will take care of displaying the data from each adapter. This modular approach allows you to add or remove sections easily, making your RecyclerView highly flexible and adaptable to different data sets.
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.
Advantages of Using ConcatAdapter in Android for Nested RecyclerViews
While the blog already covers the basics of implementing ConcatAdapter, it’s worth elaborating on the key advantages of using this approach:
Separation of Concerns:
Each adapter is responsible for a single type of data. This makes your code modular and easier to maintain.
For example, you can have one adapter for a header, another for a body, and another for a footer.
Reusability:
Individual adapters can be reused across different screens or RecyclerViews in your app. This reduces duplication and improves consistency.
This is particularly useful if you want to maintain a consistent look and feel across different parts of your application.
Performance:
By handling adapters separately, ConcatAdapter optimizes RecyclerView’s operations like binding, diffing, and recycling views.
Dynamic Content:
Easily add or remove sections from a RecyclerView by appending or detaching adapters dynamically.
Handling Click Events in Nested RecyclerViews
When working with nested RecyclerViews, handling click events can sometimes be challenging due to the layered structure. Here are some tips:
Delegate Clicks to Parent: Pass a click listener from the parent RecyclerView’s adapter to the nested RecyclerView. This ensures click events are bubbled up and handled in a central location, which is essential if you want to maintain a clean and manageable codebase.
Use Interfaces: Define an interface for click handling and implement it in the parent activity or fragment. Pass this interface to the nested RecyclerView adapter.
Use ViewModel: For apps following the MVVM architecture, bind click events to a shared ViewModel. This avoids tightly coupling the adapters with the UI.
Common Challenges and Solutions
1. RecyclerView Scrolling Performance
Problem: When multiple nested RecyclerViews are displayed, the scrolling performance might degrade.
Solution:Use setHasFixedSize(true) for both parent and child RecyclerViews. Disable nested scrolling in child RecyclerViews using setNestedScrollingEnabled(false). This ensures that the view type is managed efficiently, reducing the overhead on the RecyclerView.
2. Dynamic Data Updates
Problem: Adding or removing sections dynamically can lead to inconsistencies in the UI.
Solution: Use notifyItemRangeInserted() or notifyItemRangeRemoved() to update only the affected sections. This method ensures that only the affected sections are updated, making it more efficient to display the new data. Alternatively, use DiffUtil for efficiently managing changes in the data.
3. Data Position Confusion
Problem: Determining the correct data position in nested RecyclerViews can be tricky.
Solution: Store the parent RecyclerView’s position as a tag in the ViewHolder of the nested RecyclerView. Use that position to map data correctly.
Displaying Load State and Headers/Footers
One of the most common use cases for ConcatAdapter is displaying load state and headers/footers in a RecyclerView. By using ConcatAdapter, you can create a single adapter that displays the load state, headers, and footers, along with the main data. This makes it easier to manage the UI and provides a better user experience. For instance, you can have a header adapter that shows a loading spinner while data is being fetched, and a footer adapter that displays additional information or actions. This approach ensures that your RecyclerView is not only functional but also user-friendly and visually appealing.
Advanced Use Cases for ConcatAdapter
1. Dynamic Form Building to Display the
Use ConcatAdapter to create dynamic forms with multiple sections, such as input fields, dropdowns, and summary sections.
Each section can have its own adapter, making it easier to modify or rearrange sections.
2. E-Commerce Catalogs
Build e-commerce catalogs with sections like "Featured Products," "Trending Items," and "Recently Viewed." Each section can have its own adapter, and they can be combined seamlessly with ConcatAdapter.
3. Multimedia Lists and the View Type
Implement multimedia layouts with sections for videos, images, and articles. ConcatAdapter allows you to switch or reorder these sections easily.
Debugging Tips for Nested RecyclerViews
Log Adapter Positions:
Log adapter positions to debug unexpected behaviors, especially when dealing with dynamic data updates.
Validate Layout Managers:
Ensure each RecyclerView (parent and child) is using the correct LayoutManager.
Profile Performance:
Use Android Studio’s profiler to identify bottlenecks in scrolling or data updates.
Unit Test Adapters:
Write unit tests to verify that each adapter handles data correctly, especially when interacting with ConcatAdapter.
Final Thoughts
By elaborating on the advantages, advanced use cases, and common challenges, this additional content strengthens the blog’s value. It not only provides practical insights for developers but also establishes ConcatAdapter as a powerful and versatile tool for managing complex RecyclerView layouts in Android. Let me know if you need further assistance or additional sections!
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.
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.
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.