Expandable Recycler View in Kotlin

ChandraSaiMohan bhupathi
The Startup
Published in
4 min readJan 1, 2021

Introduction:

Android does not provide direct API for Expandable list using RecyclerView. The Android developer guide provides “ExpandableListView” that shows items in a vertically scrolling two-level list. This differs from the ListView by allowing two levels: groups which can individually be expanded to show its children. The items come from the ExpandableListAdapter associated with this view.

Disadvantages of using ListView: The ListView class is a bit too heavy — it has a lot of responsibilities. Whenever we have to handle the list, such as to configure it in some way, the only way to do this is through the ListView object or inside the adapter.

Advantages of using RecyclerView: It’s more efficient by default, the layout is separated and we have more possibilities over the data set inside the adapter.

ExpandableRecyclerView: As Android API does not directly provide ExpandableRecyclerView, it needs to be implemented by customizing RecyclerView Adapter. We need to create a customized Adapter that needs to handle expand and collapse of list manually using RecyclerView.

The example that will be described in this blog will display countries and its corresponding states.

Please find below screen shot for the same:

Expandable RecyclerView

Steps to Implement Adapter:

Step1 : Create a model . Two models to be created one for data to be displayed in list and other handling expand and collapse functionality

Step2 : The data model to be sent as input to Adapter

Step3: Implement call back methods of RecyclerView.Adapter

Step4 : Determine the type of view whether it is parent or child by getItemViewType(position: Int)

Step5 : In onCreateViewHolder() method inflate parent or child layouts based on view types.

Step6: In onBindViewHoder() method ,map data to corresponding Parent or child based on type of view.

Step7: Create two methods that implement the exact logic to expand and collapse list . These methods manually add data to the list when the list is expanded and manually remove the data when the list is collapsed. This is where the actual customization of expandable functionality is implemented manually.

Models:

Model 1: For data to be displayed .

data class StateCapital(
val countries: List<Country>
) {
data class Country(
val country: String, // India
val states: List<State>
) {
data class State(
val capital: String, // Hyderabad
val name: String // Telangana
)
}
}

Model 2: Determine expandable or collapse functionality.What data needs to be displayed when expanded and collapsed.

class ExpandableCountryModel {
companion object{
const val PARENT = 1
const val CHILD = 2

}
lateinit var countryParent: StateCapital.Country
var type : Int
lateinit var countryChild : StateCapital.Country.State
var isExpanded : Boolean
private var isCloseShown : Boolean


constructor( type : Int, countryParent: StateCapital.Country, isExpanded : Boolean = false,isCloseShown : Boolean = false ){
this.type = type
this.countryParent = countryParent
this.isExpanded = isExpanded
this.isCloseShown = isCloseShown

}


constructor(type : Int, countryChild : StateCapital.Country.State, isExpanded : Boolean = false,isCloseShown : Boolean = false){
this.type = type
this.countryChild = countryChild
this.isExpanded = isExpanded
this.isCloseShown = isCloseShown


}
}

Adapter call back methods implementation :

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
ExpandableCountryModel.PARENT -> {CountryStateParentViewHolder(LayoutInflater.from(parent.context).inflate(
R.layout.expandable_parent_item, parent, false))}

ExpandableCountryModel.CHILD -> { CountryStateChildViewHolder(LayoutInflater.from(parent.context).inflate(
R.layout.expandable_child_item, parent, false)) }

else -> {CountryStateParentViewHolder(LayoutInflater.from(parent.context).inflate(
R.layout.expandable_parent_item, parent, false))}
}
}

getItemCount:

override fun getItemCount(): Int = countryStateModelList.size

onBindViewholder:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val row = countryStateModelList[position]
when(row.type){
ExpandableCountryModel.PARENT -> {
(holder as CountryStateParentViewHolder).countryName.text = row.countryParent.country
holder.closeImage.setOnClickListener {
if (row.isExpanded) {
row.isExpanded = false
collapseRow(position)
holder.layout.setBackgroundColor(Color.WHITE)


}else{
holder.layout.setBackgroundColor(Color.GRAY)
row.isExpanded = true
holder.upArrowImg.visibility = View.VISIBLE
holder.closeImage.visibility = View.GONE
expandRow(position)
}
}
holder.upArrowImg.setOnClickListener{
if(row.isExpanded){
row.isExpanded = false
collapseRow(position)
holder.layout.setBackgroundColor(Color.WHITE)
holder.upArrowImg.visibility = View.GONE
holder.closeImage.visibility = View.VISIBLE

}
}
}


ExpandableCountryModel.CHILD -> {
(holder as CountryStateChildViewHolder).stateName.text = row.countryChild.name
holder.capitalImage.text = row.countryChild.capital
}
}

}

getViewType:

override fun getItemViewType(position: Int): Int = countryStateModelList[position].type

expandrow() method:

private fun expandRow(position: Int){
val row = countryStateModelList[position]
var nextPosition = position
when (row.type) {
ExpandableCountryModel.PARENT -> {
for(child in row.countryParent.states){
countryStateModelList.add(++nextPosition, ExpandableCountryModel(ExpandableCountryModel.CHILD, child))
}
notifyDataSetChanged()
}
ExpandableCountryModel.CHILD -> {
notifyDataSetChanged()
}
}
}

collapserow() method:

private fun collapseRow(position: Int){
val row = countryStateModelList[position]
var nextPosition = position + 1
when (row.type) {
ExpandableCountryModel.PARENT -> {
outerloop@ while (true) {
// println("Next Position during Collapse $nextPosition size is ${shelfModelList.size} and parent is ${shelfModelList[nextPosition].type}")

if (nextPosition == countryStateModelList.size || countryStateModelList[nextPosition].type == ExpandableCountryModel.PARENT) {

break@outerloop
}

countryStateModelList.removeAt(nextPosition)
}

notifyDataSetChanged()
}


}
}

Conclusion: Following the above steps Expandable List can be implemented using recyclerview

Code: The working code for above steps will be available in the below Github link:

Architecture followed: MVVM with Kotlin and Jetpack components.

--

--