Understanding and Implementing Android Lists with ListView, ExpandableListView, ListFragment, ListActivity and Custom List Adapters

There are times when you want to display data as a list. For example a list of emails, a list of messages, a list of tweets, a list of post titles, a list of photos with their titles and other meta data like dates and summary. Android provides 2 classes with the help of which we can achieve a scrollable list of items in our mobile application:

  • ListView – Show items in a vertically scrolling list.
  • ExpandableListView – Similar to ListView but supports grouping of items that can be expanded individually to show its childrens.

The list has to be populated using any Adapter of the ListAdapter type like ArrayAdapter and SimpleCursorAdapter which are the most commonly used ones.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Building a Sample List

Let’s quickly build a sample list using ArrayAdapter whose data source is an array of Java objects. Firstly, we’ll create a layout file for our ListView at /layout/activity_contacts.xml with the following code:

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Note: You can also embody the ListView in a LinearLayout. But I’ll keep it simple for now.

Then in the Activity’s onCreate method put this:

// List of items
String[] items = {
        "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7",
        "Item 8", "Item 9", "Item 10", "Item 11", "Item 12", "Item 13", "Item 14",
        "Item 15"
};
// Create an array adapter
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, items);

// Create a ListView object
ListView listView = (ListView) findViewById(R.id.listview);
// set the adapter to the view
listView.setAdapter(adapter);

That’s it! We built our first scrollable list on Android using ListView fed with data by ArrayAdapter. If you run this code on your mobile or emulator then you’ll see a list with 15 items.

The 3 arguments that we pass to the ArrayAdapter constructor are:

  • The app Context.
  • The layout that contains a TextView for instantiating views for each item. In this case we used android.R.layout.simple_list_item_1 which is a built-in XML layout in Android.
  • The list of objects as an array.

To customize the appearance we can:

  • Create a customized layout file ourselves, representing each item. For example one with an ImageView and multiple TextViews.
  • Extend the ArrayAdapter class and override the getView() method to modify the views for each items and return.

Using SimpleCursorAdapter

This adapter is supposed to be used when the data comes from a Cursor (provides random read-write access to result sets returned by a database query).

Let’s create a simple list view that’ll fetch all the contacts from your phonebook and show up in a list using SimpleCursorAdapter. We’ll create a layout file representing each item in the list at /layouts/contact_item.xml:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Text"
        android:id="@+id/contact_name"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:textSize="20dip"
        android:textStyle="bold" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Text"
        android:id="@+id/phone_number"
        android:layout_below="@+id/contact_name"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />
</RelativeLayout>

Here’s the java code that goes into the Activity:

// Columns from DB to map into the view file
String[] fromColumns = {
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER
};
// View IDs to map the columns (fetched above) into
int[] toViews = {
        R.id.contact_name,
        R.id.phone_number
};

Cursor cursor = getContentResolver().query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null,
        null,
        null,
        null
);

SimpleCursorAdapter adapter = new SimpleCursorAdapter(
        this, // context
        R.layout.contact_item, // layout file
        cursor, // DB cursor
        fromColumns, // data to bind to the UI
        toViews, // views that'll represent the data from `fromColumns`
        0
);

// Create the list view and bind the adapter
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

This should give you a list of all the contacts fetched from the Contacts Provider, displaying the display name of the contact along with the phone number.

Custom Adapter Implementation

The preceding ArrayAdapter example shows how it maps the string values of each and every item to a single view of the layout file (simple_list_item_1). If we want further control over the data assignment to layout file and support multiple views, we’ll have to create a custom adapter.

While implementing your own Adapter, you can simply extend the BaseAdapter class, but in this case we’ll extend the ArrayAdapter class itself to get the additional functionalities and benefits of it. Note: ArrayAdapter is actually a subclass of BaseAdapter.

Firstly, let’s define a new dummy class called Contacts where we can store name and numbers:

class Contacts {
    private String name;
    private String number;

    public Contacts(String name, String number) {
        this.name = name;
        this.number = number;
    }

    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public void setNumber(String number) {
        this.number = number;
    }
    public String getNumber() {
        return number;
    }
}

Next, here’s the code that’ll go into the onCreate method that creates a list of contacts using ArrayList and passes it on to our custom adapter. At the same time the custom adapter object is hooked on to the list view.

ArrayList<Contacts> contacts = new ArrayList<Contacts>();
contacts.add( new Contacts("Name 1", "9999999999") );
contacts.add( new Contacts("Name 2", "8888888888") );
contacts.add( new Contacts("Name 3", "7777777777") );
contacts.add( new Contacts("Name 4", "6666666666") );
contacts.add( new Contacts("Name 5", "5555555555") );
contacts.add( new Contacts("Name 6", "4444444444") );
contacts.add( new Contacts("Name 7", "3333333333") );
contacts.add( new Contacts("Name 8", "2222222222") );
contacts.add( new Contacts("Name 9", "1111111111") );
contacts.add( new Contacts("Name 10", "0000000000") );

CustomAdapter adapter = new CustomAdapter(this, contacts);

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

Finally, here’s our custom adapter where we override the getView() method to inflate the /layouts/contact_item.xml (created in the previous example) file into views and binding data to them according to our needs:

class CustomAdapter extends ArrayAdapter {

    private Context mContext;
    private ArrayList<Contacts> mList;

    public CustomAdapter(Context context, ArrayList<Contacts> list) {
        super(context, R.layout.contact_item, list);

        mContext = context;
        mList = list;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view;

        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.contact_item, null);
        }
        else {
            view = convertView;
        }

        TextView contactNameView = (TextView) view.findViewById(R.id.contact_name);
        TextView phoneNumberView = (TextView) view.findViewById(R.id.phone_number);

        contactNameView.setText( mList.get(position).getName() );
        phoneNumberView.setText( mList.get(position).getNumber() );

        return view;
    }
}

The getView() method is called internally where the adapter inflates the layout for each row and assign the data to individual views in the row (layout). The code inside this method is pretty straightforward:

  • Get an instance to the inflater if convertView is null and inflate the layout file.
  • Find the Views by IDs and set their data (text in this case).
  • Return the view which gets rendered in the UI.

Note: The layout inflator service can be accessed in 2 ways:

  • Via the getLayoutInflator() method on the activity object.
  • Via the context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) method call that has to be casted to (LayoutInflater) object.

When extending the BaseAdapter class to write your own Custom Adapter, if you’re using cursors then you can simply change cursor positions using moveToPosition() in the getView() method and then fetch data from it to display into the Views. We can also store the cursor data into an array (using a while loop with cursor.moveToNext() as the condition) and then use that if you don’t want to work with cursors in your adapter implementation.

ListActivity and ListFragment

If you’ve understood the basics of list views by now, then getting the hang of ListActivity and ListFragment is super easy. These 2 classes are basically a specialized activity and fragment that simplifies building and handling lists. They extend the Activity and Fragment classes respectively.

Here’s a couple of advantages with these classes:

  • We don’t have to explicitly assign a layout to them. They host a ListView, i.e., have a default layout containing a single list view. It is possible to set up our own ListView object, but it should strictly have the id @android:id/list.
  • When defining your own layout files, you can specify a TextView with the ID @android:id/empty that’ll show up when the list is empty.
  • We can call the setListAdapter() method to bind an adapter to the default ListView. Earlier, we had to get the ListView object using findViewById() and then call the setAdapter() method on that.
  • We can override the onListItemClick() method for handling the taps (selections) of list items directly.

If you have to define custom views, layouts and adapters, then it’s the same as how we did in the previous examples.

ExpandableListView

This is a view similar to ListView but allows us to define a two-level scrollable list. The first level is a bunch of groups that can be expandanded to show the children which is the second level.

In this case we must have 3 layouts:

  • First one containing the main ExpandableListView view.
  • Second one for the groups.
  • Third one for the children (detail rows).

We’ll build something based on the previous examples only. So here’s the main layout at /layout/activity_contacts.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#f4f4f4" >

    <ExpandableListView
        android:id="@+id/listView"
        android:layout_height="match_parent"
        android:layout_width="match_parent"/>

</LinearLayout>

Here’s the second layout for groups at /layout/contact_group.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">


    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
        android:textSize="17dp"
        android:text="Test" />

</LinearLayout>

Finally, here’s the layout for the child rows at /layout/contact_item.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="55dip"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="17dip"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
        android:text="Test" />

</LinearLayout>

Let’s create a class (can be an inner class within the Activity) to represent our groups and hold children.

public class Group {

    public String mName;
    public ArrayList<String> children = new ArrayList<String>();

    public Group(String name) {
        mName = name;
    }

}

Next, we’ll need to define a method inside the Activity to prepare the data. We’ll name it prepareData:

public ArrayList<Group> prepareData() {

    Group group1 = new Group("John Doe");
    group1.children.add("[email protected]");
    group1.children.add("[email protected]");

    Group group2 = new Group("Julio Boes");
    group2.children.add("[email protected]");
    group2.children.add("[email protected]");

    Group group3 = new Group("Ron Osmun");
    group3.children.add("[email protected]");
    group3.children.add("[email protected]");

    Group group4 = new Group("Angelica Tebbs");
    group4.children.add("[email protected]");
    group4.children.add("[email protected]");

    Group group5 = new Group("Temika Benning");
    group5.children.add("[email protected]");
    group5.children.add("[email protected]");


    ArrayList<Group> groups = new ArrayList<Group>();
    groups.add(group1);
    groups.add(group2);
    groups.add(group3);
    groups.add(group4);
    groups.add(group5);

    return groups;
}

In the onCreate method of our Activity, here’s how we’ll initialize the ExpandableListView along with a custom adapter that we will write in a few seconds and bind the latter to the former.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_contacts);

    // Expandable list view

    ExpandableListView listView = (ExpandableListView) findViewById(R.id.listView);

    ArrayList<Group> groups = prepareData();
    final CustomExpandableListAdapter adapter = new CustomExpandableListAdapter(this, groups);
    listView.setAdapter(adapter);
}

Finally, let’s get on to our custom adapter now. Our adapter must extend BaseExpandableListAdapter (might remind you of the equivalent BaseAdapter class for ListViews) which is an indirect subclass of ExpandableListAdapter. Like the getView() method we worked with earlier, this time we’ll need to have 2 methods:

  • getGroupView() – Returns View for the Group items/header.
  • getChildView() – Returns View for the Child items/rows.

There are certain methods that we must override from the android.widget.ExpandableListAdapter interface which is implemented by the BaseExpandableListAdapter class like getGroupCount(), getChildrenCount(), getGroup(), getChild(), getGroupId(), getChildId(), hasStableIds(), getGroupView(), getChildView() and isChildSelectable().

Time to write down our own implementation which will be pretty simple and straightforward:

public class CustomExpandableListAdapter extends BaseExpandableListAdapter {

    private Context mContext;
    private ArrayList<Group>  mGroups;
    private LayoutInflater mInflater;

    public CustomExpandableListAdapter(Context context, ArrayList<Group> groups) {
        mContext = context;
        mGroups = groups;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getGroupCount() {
        return mGroups.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return mGroups.get(groupPosition).children.size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return mGroups.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return mGroups.get(groupPosition).children.get(childPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return 0;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return 0;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.contact_group, null);
        }

        // Get the group item
        Group group = (Group) getGroup(groupPosition);

        // Set group name
        TextView textView = (TextView) convertView.findViewById(R.id.textView1);
        textView.setText(group.mName);

        return convertView;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {


        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.contact_item, null);
        }

        // Get child name
        String children = (String) getChild(groupPosition, childPosition);

        // Set child name
        TextView text = (TextView) convertView.findViewById(R.id.textView1);
        text.setText(children);

        /*convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, children, Toast.LENGTH_SHORT).show();
            }
        });*/

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}

The implementation is pretty simple and self-explanatory. You might want to setup click listeners on groups and children to show up a Toast containing the names. Here’s how you can do that in the onCreate method.

listView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
        Group group = (Group) adapter.getGroup(groupPosition);

        Toast
            .makeText(getApplicationContext(), group.mName, Toast.LENGTH_SHORT)
            .show();

        return false;
    }
});

listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        String child = (String) adapter.getChild(groupPosition, childPosition);

        Toast
            .makeText(getApplicationContext(), child, Toast.LENGTH_SHORT)
            .show();

        return false;
    }
});

We bound click listeners to groups and children respectively. There are 2 interesting things to note here:

  • We used getApplicationContext() to fetch the context to pass to the makeText() method.
  • We used getGroup() and getChild() methods on the adapter directly to get relevant data.

Conclusion

I hope this article gives you a good understanding of how to implement lists in android using various methods and in-built APIs. Feel free to intiate further discussion in the comments below.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

13 thoughts on “Understanding and Implementing Android Lists with ListView, ExpandableListView, ListFragment, ListActivity and Custom List Adapters”

  1. Thanks for this nice article.Very Helpful in learning.

    Getting an error
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.Practice.activity_life/com.Practice.activity_life.MainActivity}: java.lang.NullPointerException

    Using ExpandableListView in Navigation Drawer.
    Any help appreciated.

  2. hii Rishabh i want to use expandable navigation drawer with tab bar .so plz give me suggestion how to implement .or source code link.
    thank you

    1. Hi aayu,

      I’ll post the screenshots in a few days. Why don’t you test the code in Eclipse or Android Studio till then and run it in the emulator? Should be fairly quick 🙂

      Cheers!

      1. OK Sir, I will do that.
        Meanwhile can you tell me a solution to problem??I am running Expandable List View which I made using base adapter class BUT the problem is Groups are displaying in random order, What could be the solution??

        Sir can I get your response as email??

  3. Hi sir,

    I am amit , want to create simple listview by using set & put data with two or more columns ,but did not get it .if you have some free time then please reply me i knew you could be solved it .

    I am android beginer hope will get response earliear from u regarding that.

  4. That is for the tutorial! I have one question: how does the system store the individual IDs of the TextViews in the child layouts? I ask because I have several such TextViews in my child rows. I want to include a button in the group row that will reset all the TextViews but I can’t figure out how to access all the IDs since they are all the same ID.

Leave a Reply

Your email address will not be published. Required fields are marked *