Osborne Cox
Osborne Cox

Reputation: 466

Updating child item in ExpandableListView

I have a shopping cart in the style of an ExpandableListView.

enter image description here

I am trying to update the right drawable graphic of a child item. All the child items start out showing a "+" and I want this to change to a "-" when clicked.

I tried doing this in the adapter but when I scrolled through the list and opened up other group tabs I noticed items in those previously un-clicked items were being modified as well.

I'm not sure if I'm having an issue with view recycling in my adapter or whether something else might be causing this. Here's my code:

Custom Adapter getChildView method

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

    final View row;
    final FullMenuItemHolder fullMenuItemHolder;

    if (childView == null) {

        LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = inflater.inflate(R.layout.full_menu_child_item, parent, false);
        fullMenuItemHolder = new FullMenuItemHolder(row);
        row.setTag(fullMenuItemHolder);
    }

    else {
        row = childView;
        fullMenuItemHolder = (FullMenuItemHolder) row.getTag();
    }


    // set item name
    fullMenuItemHolder.title.setText(getChild(groupPosition, childPosition).getVenue_item_name());

    row.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            //check to see if item flagged as added to cart and set the respective drawable image
            if (!getChild(groupPosition,childPosition).getAddedToCart()) {
                getChild(groupPosition,childPosition).setAddedToCart(true);
                fullMenuItemHolder.title.setCompoundDrawablesWithIntrinsicBounds(null, null, fragment.getResources().getDrawable(R.drawable.remove), null);


            } else {
                getChild(groupPosition,childPosition).setAddedToCart(false);
                fullMenuItemHolder.title.setCompoundDrawablesWithIntrinsicBounds(null, null, fragment.getResources().getDrawable(R.drawable.add), null);

            }
        }
    });



    return row;
}

Adapter being set in Fragment (called from onPostExecute in asyncTask after server download)

 fullMenuExpListAdapter = new FullMenuExpListAdapter(this.getActivity(), listHeaderNames, listChildItems, this);
 expandableListView.setAdapter(fullMenuExpListAdapter);

Upvotes: 3

Views: 3949

Answers (1)

vzsg
vzsg

Reputation: 2896

The row views are getting reused by the ListView, which is why the clicks appear to change multiple rows. If you were to wiggle the list a little more, you'd also notice that plus/minus signs are actually random, it doesn't matter which ones were clicked on before.

After this line, you should manually update the compound drawables, similarly to your OnClickListener:

// set item name
fullMenuItemHolder.title.setText(getChild(groupPosition, childPosition).getVenue_item_name());

This ensures that both new and reused rows show the correct icon.


(My reply to your comment was getting too long.)

There's actually nothing random to this issue, it was just a shortcut in the explanation for me. :)

Think of it this way: any row that leaves the screen completely is thrown on the recycling pile the moment the last pixel is scrolled out. It is left as it is - no subviews are automatically cleared or modified.

When a new row enters the screen, the ListView first checks the pile for leftovers (of the required view type). If found, your adapter is given a single chance to update everything with the new data. If you forget to update anything, you'll be left with remnants of the earlier item - like the drawable in your original question.

But keep in mind that this doesn't only apply to visual state, but behavior as well!

If you didn't create a new OnClickListener instance (e.g. you put the code in the if above), the groupPosition and childPosition would never change from the initial values, so clicking a row might update a completely different model object! Even though the UI would work seemingly right, the model would be inconsistent.

It might be useful to move the view updating logic from the Adapter to your ViewHolder, and to give it a single bindModel(ChildType child) method that sets the title, listeners, etc. It's easier to read and verify that everything is appropriately set each time.
(The new RecyclerView works similarly, you should look it up.)


tl;dr: You must make sure in your Adapter implementation to update everything that depends on the row's data model, or dragons will consume us all.

Upvotes: 2

Related Questions