Java – Android ExpandableListView is populated twice

Android ExpandableListView is populated twice… here is a solution to the problem.

Android ExpandableListView is populated twice

I’m using an extensible ListView to display options and sub-options in menus, respectively. The data entered is correct, but oddly enough, the listview seems to call getChild and getChildView twice for each data entry, so it is added to the View twice. It traverses and then seems to traverse again.

Example:

Category:
-toy
– Electronics
-Car
-toy
– Electronics
– Cars

Anticipate:
Category:
-toy
– Electronics
– Cars

I’m new to Android and Java, so I probably made a newbie mistake. I’ve tried step by step to find the point where it copies the data, but all I can find is getChild/getChildView being called twice.

This is the expandableListView adapter class:

    public class ExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private ArrayList<String> filterHeaderList;
     child data in format of header title, child title
    private HashMap<String, ArrayList<JSONObject>> filterOptionsMap;

public ExpandableListAdapter(Context context, ArrayList<String> filterHeaders, HashMap<String, ArrayList<JSONObject>> filterChildListMap) {
        this.context = context;
        this.filterHeaderList = filterHeaders;
        this.filterOptionsMap = filterChildListMap;
    }

@Override
    public String[] getChild(int groupPosition, int childPosititon) {
        JSONObject childObject = this.filterOptionsMap.get(this.filterHeaderList.get(groupPosition)).get(childPosititon);
        if (childObject != null) {
            Iterator<?> it = childObject.keys();
            String key = null;
            String value = null;
            if (it.hasNext()) {
                key = it.next().toString();
                try {
                    value = childObject.getString(key);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                String[] data = new String[2];
                data[0] = key;
                data[1] = value;
                return data;
            } else {
                return null;
            }
        }
        return null;
    }

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

@Override
    public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        String childNum = null;
        String childText = null;
        final String[] childData = (String[]) getChild(groupPosition, childPosition);
        if (childData != null) {
            childNum = childData[0];
            childText = childData[1];
        }
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.ps_filter_sort_filter_expandable_child, null);
        }

TextView tvChildTitle = (TextView) convertView.findViewById(R.id.ps_filter_sort_filter_expandable_child_title);

tvChildTitle.setText(childText);
        tvChildTitle.setTag(R.id.ps_filter_sort_filter_expandable_child_title + groupPosition + childPosition, childNum);
        return convertView;
    }

@Override
    public int getChildrenCount(int groupPosition) {
        return this.filterOptionsMap.get(this.filterHeaderList.get(groupPosition)).size();
    }

@Override
    public String getGroup(int groupPosition) {
        return this.filterHeaderList.get(groupPosition);
    }

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

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

@Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        String headerTitle = (String) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.ps_filter_sort_filter_expandable_parent, null);
        }

TextView tvGroupTitle = (TextView) convertView.findViewById(R.id.ps_filter_sort_filter_expandable_parent_title);
        tvGroupTitle.setTypeface(null, Typeface.BOLD);
        tvGroupTitle.setText(headerTitle);

return convertView;
    }

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

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

Then I call it like this:

filterListAdapter = new ExpandableListAdapter(this.getActivity(), filterHeaderList, filterOptionsMap);
         setting list adapter
        expListView.setAdapter(filterListAdapter);
        filterListAdapter.notifyDataSetChanged();

There’s a reason the data is organized this way, and I don’t want to delve into it. What I can say is that the HashMap “filterOptionsMap” is correct and there is no duplication in it or “filterHeaderList”

Any help would be appreciated. I can’t find anyone experiencing a similar problem, for which I’ve been typing on the keyboard.

Thank you.

Postscript.
You’ll also need to tap the group header twice to collapse it, but tap once to open it. I don’t know if it’s related or expected behavior, but I’d like to be able to turn it on or off with just one tap. Bonus points for helping in this regard.

What is required here is some additional code related to HashMap:

private void prepareFilterListData(ArrayList<String> headerList, JSONObject[] categoryList, JSONObject[] sellerList, JSONObject[] typeList) {
    filterHeaderArrayList = headerList;
    filterOptionsMap = new HashMap<String, ArrayList<JSONObject>>();

 Adding child data
    ArrayList<JSONObject> categories = new ArrayList<JSONObject>();
    if (categoryList != null && categoryList.length > 0) {
        for (JSONObject category : categoryList) {
            categories.add(category);
        }
    }

ArrayList<JSONObject> sellers = new ArrayList<JSONObject>();
    if (sellerList != null && sellerList.length > 0) {
        for (JSONObject seller : sellerList) {
            sellers.add(seller);
        }
    }

ArrayList<JSONObject> types = new ArrayList<JSONObject>();
    if (typeList != null && typeList.length > 0) {
        for (JSONObject type : typeList) {
            types.add(type);
        }
    }

filterOptionsMap.put(filterHeaderArrayList.get(0), categories);
    filterOptionsMap.put(filterHeaderArrayList.get(1), sellers);
    filterOptionsMap.put(filterHeaderArrayList.get(2), types);
}

filterHeaderList = Directory.getFilterOptions();
    filterCategoryList = Directory.getFilterCategories();
    filterSellerList = Directory.getFilterSellers();
    filterTypeList = Directory.getFilterTypes();

 Forms data structures from given input
    if (filterHeaderList != null && filterCategoryList != null && filterSellerList != null && filterTypeList != null) {
        prepareFilterListData(filterHeaderList, filterCategoryList, filterSellerList, filterTypeList);

private JSONObject[] getFilterTypes(JSONObject productSearchSettings, String filterTypes) {
    JSONArray typesObj;
    JSONObject[] types;
    try {
        typesObj = productSearchSettings.optJSONArray(filterTypes);
        if (typesObj != null) {
            types = new JSONObject[typesObj.length()];
            for (int i = 0; i < typesObj.length(); i++) {
                String key = "type" + String.valueOf(i);
                String val = (String) typesObj.get(i).toString();
                types[i] = new JSONObject().put(key, val);
            }
            return types;
        } else {
            return null;
        }
    } catch (JSONException e) {
         TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

private JSONObject[] getFilterSellers(JSONObject productSearchSettings, String filterSellers) {
    JSONObject sellersObj = null;
    JSONObject[] sellers;
    try {
        sellersObj = productSearchSettings.getJSONObject(filterSellers);
        if (sellersObj != null) {
            sellers = new JSONObject[sellersObj.length()];
            Iterator<?> it = sellersObj.keys();
            int i = 0;
            while (it.hasNext()) {
                String key = (String) String.valueOf(it.next().toString());
                String val = sellersObj.getString(key);
                sellers[i] = new JSONObject().put(key, val);
                i++;
            }
            return sellers;
        } else {
            return null;
        }
    } catch (JSONException e) {
         TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

private JSONObject[] getFilterCategories(JSONObject productSearchSettings, String filterCategories) {
    JSONObject categoriesObj = null;
    JSONObject[] categories;
    try {
        categoriesObj = productSearchSettings.getJSONObject(filterCategories);
        if (categoriesObj != null) {
            categories = new JSONObject[categoriesObj.length()];
            Iterator<?> it = categoriesObj.keys();
            int i = 0;
            while (it.hasNext()) {
                String key = (String) String.valueOf(it.next().toString());
                String val = categoriesObj.getString(key);
                categories[i] = new JSONObject().put(key, val);
                i++;
            }
            return categories;
        } else {
            return null;
        }
    } catch (JSONException e) {
         TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;

}

As mentioned earlier, but the hash map that goes into the adapter is correct. I verified by debugging.

Solution

I finally found it. Thank you very much for your feedback and help.

In the group’s hit event, I didn’t return true to indicate that the click was processed. I’m using the click event to toggle the expansion.

I don’t know why this causes duplicates and it takes two more hits to close the group, but toggling that return for that group’s click event makes a big difference.

Related Problems and Solutions