BaseExpandableListAdapter in Android with Example
The BaseExpandableListAdapter in Android is an abstract class used to create custom adapters for ExpandableListView, which displays a list of grouped items that can be expanded or collapsed. It provides methods to manage group and child views, define the count of groups and their children, and bind data to the views.
In many android apps, the developer may need to show multi-data for huge main data items. i.e. as per our example, under "Programming languages", we need to show "Python", "Java" etc., and under "Relational database" we need to show "Oracle", "MySQL' etc., For that purpose, we can use "BaseExpandableListAdapter". It is a bridge between the UI component and the data source which fills data in the UI component. It holds the data and then sends the data to the Adapter view then the view can take the data from the Adapter view and shows the data on different views like ExpandableListView. It will provide access to the data of the children (categorized by groups), and also instantiate views for the children and groups. A sample GIF is given below to get an idea about what we are going to do in this article.
Note that we are going to implement this project using both the language Java and Kotlin.
Here is the code snippet for the CustomizedAdapter file in both Java and Kotlin:
CustomizedAdapter File:
package org.geeksforgeeks.demo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class CustomizedAdapter extends BaseExpandableListAdapter {
private Context context;
private ArrayList<GroupInformation> mainSetName;
public CustomizedAdapter(Context context, ArrayList<GroupInformation> deptList) {
this.context = context;
this.mainSetName = deptList;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
ArrayList<ChildInfo> productList = mainSetName.get(groupPosition).getSubsetName();
return productList.get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View view, ViewGroup parent) {
ChildInfo detailInfo = (ChildInfo) getChild(groupPosition, childPosition);
if (view == null) {
LayoutInflater infalInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = infalInflater.inflate(R.layout.child_items, null);
}
TextView childItem = (TextView) view.findViewById(R.id.childItm);
childItem.setText(detailInfo.getName().trim());
return view;
}
@Override
public int getChildrenCount(int groupPosition) {
ArrayList<ChildInfo> productList = mainSetName.get(groupPosition).getSubsetName();
return productList.size();
}
@Override
public Object getGroup(int groupPosition) {
return mainSetName.get(groupPosition);
}
@Override
public int getGroupCount() {
return mainSetName.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isLastChild, View view,
ViewGroup parent) {
GroupInformation headerInfo = (GroupInformation) getGroup(groupPosition);
if (view == null) {
LayoutInflater inf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inf.inflate(R.layout.group_items, null);
}
TextView heading = (TextView) view.findViewById(R.id.data);
heading.setText(headerInfo.getName().trim());
return view;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
package org.geeksforgeeks.demo
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import android.widget.TextView
class CustomizedAdapter(
private val context: Context,
private val mainSetName: ArrayList<GroupInformation>
) : BaseExpandableListAdapter() {
override fun getChild(groupPosition: Int, childPosition: Int): Any {
val productList = mainSetName[groupPosition].subsetName
return productList[childPosition]
}
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return childPosition.toLong()
}
override fun getChildView(
groupPosition: Int, childPosition: Int, isLastChild: Boolean,
convertView: View?, parent: ViewGroup
): View {
var view = convertView
val detailInfo = getChild(groupPosition, childPosition) as ChildInfo
if (view == null) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
view = inflater.inflate(R.layout.child_items, parent, false)
}
val childItem = view!!.findViewById<TextView>(R.id.childItm)
childItem.text = detailInfo.name.trim()
return view
}
override fun getChildrenCount(groupPosition: Int): Int {
val productList = mainSetName[groupPosition].subsetName
return productList.size
}
override fun getGroup(groupPosition: Int): Any {
return mainSetName[groupPosition]
}
override fun getGroupCount(): Int {
return mainSetName.size
}
override fun getGroupId(groupPosition: Int): Long {
return groupPosition.toLong()
}
override fun getGroupView(
groupPosition: Int, isExpanded: Boolean,
convertView: View?, parent: ViewGroup
): View {
var view = convertView
val headerInfo = getGroup(groupPosition) as GroupInformation
if (view == null) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
view = inflater.inflate(R.layout.group_items, parent, false)
}
val heading = view!!.findViewById<TextView>(R.id.data)
heading.text = headerInfo.name.trim()
return view
}
override fun hasStableIds(): Boolean {
return true
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return true
}
}
Note: Check out the methods getChildView() and getGroupView(). They are used to create the View corresponding to the layout.
Key Methods in BaseExpandableListAdapter
Method | Description |
---|---|
getGroupCount() | Returns the total number of groups. |
getChildrenCount(int groupPosition) | Returns the number of children in a specific group. |
getGroup(int groupPosition) | Gets the data associated with the group at the specified position. |
getChild(int groupPosition, int childPosition) | Gets the child data for the given group position. |
getGroupId(int groupPosition) | Returns the ID for the group. |
getChildId(int groupPosition, int childPosition) | Returns the child ID for the specified group. |
getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) | Provides the view for a group item. |
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) | Provides the view for a child item. |
hasStableIds() | Indicates whether IDs are stable across changes. |
isChildSelectable(int groupPosition, int childPosition) | Indicates whether a child is selectable. |
Steps Implementation of BaseExpandableListAdapter
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.
Note that select Java/Kotlin as the programming language.
Step 2: Working with the activity_main.xml file
Go to the activity_main.xml file and refer to the following code. Below is the code for the activity_main.xml file.
activity_main.xml:
<?xml version="1.0" encoding="UTF-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<ExpandableListView
android:id="@+id/simpleExpandableListView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/grey"
android:dividerHeight="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Layout:
Step 3: Create new XML files
Go to the app > res > layout > right-click > New > Layout Resource File and name the file as child_items. Below is the code for the child_items.xml file. Here TextView is used for a subset of items Eg: Python.
child_items.xml:
<?xml version="1.0" encoding="utf-8"?>
<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:background="@color/white"
android:orientation="vertical">
<TextView
android:id="@+id/childItm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="15dp"
android:textAppearance="?android:attr/textAppearanceMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Similarly, create another layout resource file and name the file as group_items. Below is the code for the group_items.xml file. Here TextView is used for the main set of items Eg: Programming_Languages.
group_items.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="35sp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Create new Java/Kotlin files
Go to the app > java > your package name > right-click > New > Java/Kotlin Class and name the file as ChildInfo. Below is the code for the ChildInfo file.
ChildInfo File:
package org.geeksforgeeks.demo;
public class ChildInfo {
private String name = "";
// Getter , setter methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package org.geeksforgeeks.demo
class ChildInfo {
var name: String = ""
}
Similarly, create another java class file and name the file as CustomizedAdapter. We have discussed this in the beginning section and also each overridden method. So you may copy the same code and implement it in the project.
Now create another java file and name the file as GroupInformation. Below is the code for the GroupInformation file in both Java and Kotlin.
GroupInformation File:
package org.geeksforgeeks.demo;
import java.util.ArrayList;
public class GroupInformation {
private String mainSetName;
private ArrayList<ChildInfo> list = new ArrayList<ChildInfo>();
public String getName() {
return mainSetName;
}
public void setName(String mainSetName) {
this.mainSetName = mainSetName;
}
public ArrayList<ChildInfo> getSubsetName() {
return list;
}
public void setSubsetName(ArrayList<ChildInfo> subSetName) {
this.list = subSetName;
}
}
package org.geeksforgeeks.demo
class GroupInformation {
var name: String? = null
var subsetName: ArrayList<ChildInfo> = ArrayList()
}
Step 5: Working with the MainActivity file
Go to the MainActivity file and refer to the following code. Below is the code for the MainActivity file in both Java and Kotlin. Comments are added inside the code to understand the code in more detail.
MainActivity File:
package org.geeksforgeeks.demo;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.LinkedHashMap;
public class MainActivity extends AppCompatActivity {
private LinkedHashMap<String, GroupInformation> mainSet = new LinkedHashMap<String, GroupInformation>();
private ArrayList<GroupInformation> subSet = new ArrayList<GroupInformation>();
private CustomizedAdapter listAdapter;
private ExpandableListView simpleExpandableListView1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// add data for displaying in expandable list view
loadData();
// get reference of the ExpandableListView from activity_main
simpleExpandableListView1 = (ExpandableListView) findViewById(R.id.simpleExpandableListView1);
// create the adapter and by passing your ArrayList data
listAdapter = new CustomizedAdapter(MainActivity.this, subSet);
simpleExpandableListView1.setAdapter(listAdapter);
// setOnChildClickListener listener for child row click, so that we can get the value
simpleExpandableListView1.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
// get the group header
GroupInformation headerInfo = subSet.get(groupPosition);
// get the child info
ChildInfo detailInfo = headerInfo.getSubsetName().get(childPosition);
// display it or do something with it
Toast.makeText(getBaseContext(), headerInfo.getName() + "/" + detailInfo.getName(), Toast.LENGTH_LONG).show();
return false;
}
});
// setOnGroupClickListener listener for group heading click
simpleExpandableListView1.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
// get the group header
GroupInformation headerInfo = subSet.get(groupPosition);
// display it or do something with it
Toast.makeText(getBaseContext(), headerInfo.getName(), Toast.LENGTH_LONG).show();
return false;
}
});
}
// load some initial data into out list
private void loadData() {
addDetails("Programming_Languages", "Python");
addDetails("Programming_Languages", "Java");
addDetails("Programming_Languages", "Kotlin");
addDetails("Programming_Languages", "NodeJS");
addDetails("Programming_Languages", "GO");
addDetails("Relational_Database", "Oracle");
addDetails("Relational_Database", "SQLServer");
addDetails("Relational_Database", "MySQL");
addDetails("NoSQL_Database", "MongoDB");
addDetails("NoSQL_Database", "Cassandra");
addDetails("NoSQL_Database", "CouchDB");
}
// here we maintain main set like Programming languages and subsets like Python
private int addDetails(String mainSet, String subSet) {
int groupPosition = 0;
// check the hash map if the group already exists
GroupInformation headerInfo = this.mainSet.get(mainSet);
// add the group if doesn't exists
if (headerInfo == null) {
headerInfo = new GroupInformation();
headerInfo.setName(mainSet);
this.mainSet.put(mainSet, headerInfo);
this.subSet.add(headerInfo);
}
// get the children for the group
ArrayList<ChildInfo> subList = headerInfo.getSubsetName();
// size of the children list
int listSize = subList.size();
// add to the counter
listSize++;
// create a new child and add that to the group
ChildInfo detailInfo = new ChildInfo();
detailInfo.setName(subSet);
subList.add(detailInfo);
headerInfo.setSubsetName(subList);
// find the group position inside the list
groupPosition = this.subSet.indexOf(headerInfo);
return groupPosition;
}
}
package org.geeksforgeeks.demo
import android.os.Bundle
import android.view.View
import android.widget.ExpandableListView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val mainSet = LinkedHashMap<String, GroupInformation>()
private val subSet = ArrayList<GroupInformation>()
private var listAdapter: CustomizedAdapter? = null
private var simpleExpandableListView1: ExpandableListView? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// add data for displaying in expandable list view
loadData()
// get reference of the ExpandableListView from activity_main
simpleExpandableListView1 =
findViewById<View>(R.id.simpleExpandableListView1) as ExpandableListView
// create the adapter and by passing your ArrayList data
listAdapter = CustomizedAdapter(this@MainActivity, subSet)
simpleExpandableListView1!!.setAdapter(listAdapter)
// setOnChildClickListener listener for child row click, so that we can get the value
simpleExpandableListView1!!.setOnChildClickListener { parent, v, groupPosition, childPosition, id -> // get the group header
val headerInfo = subSet[groupPosition]
// get the child info
val detailInfo = headerInfo.subsetName[childPosition]
// display it or do something with it
Toast.makeText(baseContext, headerInfo.name + "/" + detailInfo.name, Toast.LENGTH_SHORT)
.show()
false
}
// setOnGroupClickListener listener for group heading click
simpleExpandableListView1!!.setOnGroupClickListener { parent, v, groupPosition, id -> // get the group header
val headerInfo = subSet[groupPosition]
// display it or do something with it
Toast.makeText(baseContext, headerInfo.name, Toast.LENGTH_SHORT).show()
false
}
}
// load some initial data into out list
private fun loadData() {
addDetails("Programming_Languages", "Python")
addDetails("Programming_Languages", "Java")
addDetails("Programming_Languages", "Kotlin")
addDetails("Programming_Languages", "NodeJS")
addDetails("Programming_Languages", "GO")
addDetails("Relational_Database", "Oracle")
addDetails("Relational_Database", "SQLServer")
addDetails("Relational_Database", "MySQL")
addDetails("NoSQL_Database", "MongoDB")
addDetails("NoSQL_Database", "Cassandra")
addDetails("NoSQL_Database", "CouchDB")
}
// here we maintain main set like Programming languages and subsets like Python
private fun addDetails(mainSet: String, subSet: String): Int {
var groupPosition = 0
// check the hash map if the group already exists
var headerInfo = this.mainSet[mainSet]
// add the group if doesn't exists
if (headerInfo == null) {
headerInfo = GroupInformation()
headerInfo.name = mainSet
this.mainSet[mainSet] = headerInfo
this.subSet.add(headerInfo)
}
// get the children for the group
val subList = headerInfo.subsetName
// size of the children list
var listSize = subList.size
// add to the counter
listSize+=1
// create a new child and add that to the group
val detailInfo = ChildInfo()
detailInfo.name = subSet
subList.add(detailInfo)
headerInfo.subsetName = subList
// find the group position inside the list
groupPosition = this.subSet.indexOf(headerInfo)
return groupPosition
}
}
Output:
On running the app, on the emulator, we can able to view the output as attached in the video. This feature is a much-required feature across many apps.