Written on March 30, 2016
Author: Lewis Gavin

Android - List View Adapters

In this post I will be deviating slightly from the usual Big Data posts to talk about some pragmatism within my in progress android application; Gymify. The app is a gym session tracking application. Initially it is being developed for my own personal use, but I may publish it if I feel it gets to a good enough state. In reality it’s a project to keep my programmer brain active.

The app makes use of lists, within lists, within lists. A list of days(that are essentially routines), that contain a list of Exercises for that Day, with each Exercise containing a list of Sets/Activities performed per the current session. Due to lists being a common theme within my application, I wanted a nice way to use get the optimal amount of re-use. The rest of this post will demonstrate how I did this.

List View Adapters

ListViewAdapters are a common pattern used in android development when an application wishes to represent some information as a list.

Some applications may have many lists. It is possible that these lists will all have the same general style. In my case, it will also mean that clicking an item in one list, will take you to another list.

The Java objects that are represented as items within a list may differ for each list, for example you may have a list of Car objects. When a car is selected you may wish to open a new list that contains location objects, stating where the car may be sold. In my case, I’ll have a Day object, an Exercise object and a Set object.

The overall look and feel of these lists, along with a set of actions such as re-ordering or deleting will be common across them all. The differences will include the type of object contained within them and the way the information is displayed for each item.

List View Adapter Template

To avoid code duplication, you can set up an adapter template class that contains the core functionality. This class can then be inherited so child classes can make the most of these core features. The beauty of it being Templated allows the children to have different object types contained within the list.

During the development of my Gymify application, I used a ListViewAdapter template class as follows:


public abstract class ListViewAdapter<T> extends ArrayAdapter<T> {

    // objects to hold the list, contect and layout inflator (used later)
    protected List<T> list;
    protected Context context;
    protected LayoutInflater inflater;

    /* SparseBooleanArray that will be used to hold whether a list item has been selected or not
    this functionality will allow the user to select multiple list items and perform an action
    such as delete on multiple list items */
    private SparseBooleanArray mSelectedItemsIds;

    //basic constructor
    public ListViewAdapter(Context context, int resourceId, List<T> list) {
        super(context, resourceId, list);
        mSelectedItemsIds = new SparseBooleanArray();
        this.list = list;
        this.context = context;
        inflater = LayoutInflater.from(context);
    }

    // abstract getView function that must be defined by implementing classes
    public abstract View getView(int position, View view, ViewGroup parent);

    // function to remove items from the list
    public void remove(T object) {
        list.remove(object);
        notifyDataSetChanged();
    }

    // toggle a list item as selected or not selected
    public void toggleSelection(int position) {
        selectView(position, !mSelectedItemsIds.get(position));

    }

    //function used to cancel selection of items and reset the SparseBooleanArray
    public void removeSelection() {
        mSelectedItemsIds = new SparseBooleanArray();
        notifyDataSetChanged();
    }

    //function to add or remove list items to the selected items array
    public void selectView(int position, boolean value) {

        if (value){
            mSelectedItemsIds.put(position, value);
        }
        else{
            mSelectedItemsIds.delete(position);
        }
        notifyDataSetChanged();
    }

    //return the number of selected items
    public int getSelectedCount() {
        return mSelectedItemsIds.size();
    }

    //return a SparseBooleanArray of selected items
    public SparseBooleanArray getSelectedIds() {
        return mSelectedItemsIds;
    }

}

What I have implemented here is the skeleton for all other ListViewAdapter classes to extend on. This means that each ListView will have the multi-selection and deletion capabilites (this functionality is performed by a MultiSelectListener class that uses these helper functions), it will be inherently built in. However the way each list item is displayed, will be different for each ListView; hence the abstract getView function. This enforces the children classes to implement this themselves.

Due to the class containing an abstract function. The class itself must be abstract, meaning it cannot be instantiated, but that’s ok, as I am going to make a different ListView class for each android Activity that will host my list.

DayListViewAdapter

The DayListViewAdapter class is one of the classes I used to inherit from my ListViewAdapter. This class is now super simple. All it needs to do is provide a constructor that will simply call the super constructor and ensure it implements the getView function as part of the ListViewAdapter contract.

My implementation is as follows - it uses the parent inflater to create its View object passing in the xml layout file. This list item will simply show the name of the Day/Routine and on the far right, the day of the week that this routine is commonly performed on.

Heres a screenshot of what this will look like.

DayListView

The code for this class as I said is really simple.


public class DayListViewAdapter extends ListViewAdapter<Day>{

    public DayListViewAdapter(Context context, int resourceId, List<Day> list) {
        super(context, resourceId, list);
    }
    
    private class ViewHolder {
        
        TextView day;
        TextView weekday;
    }
 
    public View getView(int position, View view, ViewGroup parent) {

        final ViewHolder holder;
        if (view == null) {
            holder = new ViewHolder();
            view = inflater.inflate(R.layout.day_list_item_layout, null);
            // Locate the TextViews in listview_item.xml
            holder.day = (TextView) view.findViewById(R.id.dayName);
            holder.weekday = (TextView) view.findViewById(R.id.weekday);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }
        // Capture position and set to the TextViews
        holder.day.setText(list.get(position).getName());
        holder.weekday.setText((Day.weekdays[list.get(position).getWeekday().getMask()]));
        
        return view;
    }
}

Outro

Hopefully this was helpful. I implemented the same style for my ExerciseListViewAdapter and made a CardAdapter class for recording Sets/Activities. Maybe I’ll write a post on the generic Card View in the near future, as this was a really useful way to replicate the Google Card View layout before the API was available.