title: Android ExpandableListView populated twice
body: <p>I am using an expandable list view to display options and sub options respectively in a menu. The data coming in is correct, but oddly the listview seems to call the getChild and getChildView twice for each data entry and thus it is added to the view twice. It iterates through and then seems to iterate through again.</p>
  
  <p>Example:</p>
  
  <p>Categories:
   -Toys
   -Electronics
   -Cars
   -Toys
   -Electronics
   -Cars</p>
  
  <p>Expected:
  Categories:
   -Toys
   -Electronics
   -Cars</p>
  
  <p>I am really new to Android and Java so I am probably making a rookie mistake. I have stepped through to try and find a point where it would be duplicating the data, but all I could find is that getChild/getChildView is being called twice.</p>
  
  <p>Here is the expandableListView adapter class:</p>
  
  <pre><code>    public class ExpandableListAdapter extends BaseExpandableListAdapter {
      private Context context;
      private ArrayList&lt;String&gt; filterHeaderList;
      // child data in format of header title, child title
      private HashMap&lt;String, ArrayList&lt;JSONObject&gt;&gt; filterOptionsMap;
  
      public ExpandableListAdapter(Context context, ArrayList&lt;String&gt; filterHeaders, HashMap&lt;String, ArrayList&lt;JSONObject&gt;&gt; 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&lt;?&gt; 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;
      }
  }
  </code></pre>
  
  <p>I then call it like this:</p>
  
  <pre><code>filterListAdapter = new ExpandableListAdapter(this.getActivity(), filterHeaderList, filterOptionsMap);
          // setting list adapter
          expListView.setAdapter(filterListAdapter);
          filterListAdapter.notifyDataSetChanged();
  </code></pre>
  
  <p>There are reasons the data is organized in this fashion which I'd rather not get into. What I can say is that the HashMap "filterOptionsMap" is correct and there is no duplication in it or the "filterHeaderList"</p>
  
  <p>Any help would be greatly appreciated. I have not been able to find someone having a similar issue and I've been banging my head on the keyboard over this.</p>
  
  <p>Thank you.</p>
  
  <p><strong>P.S.
  You also need to tap the group heading twice to get it to collapse, but once to open. I don't know if it is related or expected behavior, but I would hope to have it be a single tap to toggle it open or closed. Bonus points for help with that as well.</strong></p>
  
  <p>As requested here is some additional code related to the HashMap:</p>
  
  <pre><code>private void prepareFilterListData(ArrayList&lt;String&gt; headerList, JSONObject[] categoryList, JSONObject[] sellerList, JSONObject[] typeList) {
      filterHeaderArrayList = headerList;
      filterOptionsMap = new HashMap&lt;String, ArrayList&lt;JSONObject&gt;&gt;();
  
      // Adding child data
      ArrayList&lt;JSONObject&gt; categories = new ArrayList&lt;JSONObject&gt;();
      if (categoryList != null &amp;&amp; categoryList.length &gt; 0) {
          for (JSONObject category : categoryList) {
              categories.add(category);
          }
      }
  
      ArrayList&lt;JSONObject&gt; sellers = new ArrayList&lt;JSONObject&gt;();
      if (sellerList != null &amp;&amp; sellerList.length &gt; 0) {
          for (JSONObject seller : sellerList) {
              sellers.add(seller);
          }
      }
  
      ArrayList&lt;JSONObject&gt; types = new ArrayList&lt;JSONObject&gt;();
      if (typeList != null &amp;&amp; typeList.length &gt; 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 &amp;&amp; filterCategoryList != null &amp;&amp; filterSellerList != null &amp;&amp; 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 &lt; 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&lt;?&gt; 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&lt;?&gt; 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;
  
  }
  </code></pre>
  
  <p>As mentioned before, however the hashmap going into the adapter is correct. I verified through debugging.</p>