2

I'm using ListView to show list of some elements for which I need to count things. My list view looks like this

enter image description here

The value "3" gets updated when I click on plus and minus button or reset button. This is how I achieve this in my CustomListViewAdapter

holder.plusButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            ViewGroup parent = (ViewGroup) v.getParent();
            TextView tv = (TextView) parent.findViewById(R.id.count);

            int count = Integer.parseInt((String) tv.getText());
            count++;
            tv.setText(String.valueOf(count));
            int currentVal = Integer.parseInt(totalCount.getText().toString());
            totalCount.setText(String.valueOf(currentVal + 1));
        }
    });

where I'm using ViewHolder pattern.

Now the problem is, after I update the value, it's not getting updated in adapter. i.e when I try to loop over adapter, I still get the value as 3 which I set initially rather than updated value. Am I doing it right way or is there a better way of doing it. how do I make sure that adapter has the updated value.

2nd Question: As the adapter is not getting updated, when I'm trying to retrieve the values from list to send it somewhere, I get nullpointer when I do this

view = listView.getChildAt(i).findViewById(R.id.count);

for views in list which are not visible on screen. i.e if I have 10 such rows, only 5 will be visible on screen and when I try to access 6th, I get null pointer for above code block. How should I be accessing the listview values then?

EDIT: My getView method source code is

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    Counter rowItem = getItem(position);

    LayoutInflater mInflater = (LayoutInflater) context
            .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.counter_list, null);
        holder = new ViewHolder();

        convertView.findViewById(R.id.parentLayoutRow).setBackgroundColor(Color.parseColor(rowItem.getPrimaryColor()));

        holder.resetButton = (Button) convertView.findViewById(R.id.buttonReset);
        holder.counterName = (TextView) convertView.findViewById(R.id.counterName);
        holder.counterDesc = (TextView) convertView.findViewById(R.id.counterDesc);
        holder.count = (TextView) convertView.findViewById(R.id.count);
        holder.minusButton = (Button) convertView.findViewById(R.id.buttonMinus);
        holder.plusButton = (Button) convertView.findViewById(R.id.buttonPlus);

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.resetButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            ViewGroup parent = (ViewGroup) v.getParent();
            TextView tv = (TextView) parent.findViewById(R.id.count);

            int currentVal = Integer.parseInt(totalCount.getText().toString());
            totalCount.setText(String.valueOf(currentVal - Integer.parseInt(tv.getText().toString())));

            tv.setText(String.valueOf(0));
        }
    });
    holder.counterName.setText(rowItem.getName());
    holder.counterDesc.setText(rowItem.getDesc());
    holder.count.setText(String.valueOf(rowItem.getCount()));
    holder.resetButton.setBackgroundColor(Color.parseColor(rowItem.getButtonColor()));
    holder.minusButton.setBackgroundColor(Color.parseColor(rowItem.getButtonColor()));
    holder.minusButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            ViewGroup parent = (ViewGroup) v.getParent();
            TextView tv = (TextView) parent.findViewById(R.id.count);

            int count = Integer.parseInt((String) tv.getText());

            if (count > 0) {
                count--;
                tv.setText(String.valueOf(count));
                int currentVal = Integer.parseInt(totalCount.getText().toString());
                totalCount.setText(String.valueOf(currentVal - 1));
            } 
        }
    });
    holder.plusButton.setBackgroundColor(Color.parseColor(rowItem.getButtonColor()));
    holder.plusButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            ViewGroup parent = (ViewGroup) v.getParent();
            TextView tv = (TextView) parent.findViewById(R.id.count);

            int count = Integer.parseInt((String) tv.getText());
            count++;
            tv.setText(String.valueOf(count));
            int currentVal = Integer.parseInt(totalCount.getText().toString());
            totalCount.setText(String.valueOf(currentVal + 1));
        }
    });

    return convertView;
}

now how do I update the adapter value in this function holder.minusButton.setOnClickListener(new OnClickListener()

2 Answers2

3

The Views in a ListView don't retain their state. As ViewHolder shows you they get thrown away when they leave the screen and rebuilt later: they are always driven by the value in the Adapter.

So what you want to do is find the entry in the Adapter backing the list and update THAT, then have the ListView redraw itself. Have a look at ListView.getAdapter(), then Adapter.getItem(). You'll note that there's no Adapter.setItem(), and you can't change the value of an existing String or Integer (they're immutable) so your list item needs to be an Object which wraps the value. Or you could use an AtomicInteger which has an increment method. You will then need to call ListView.notifyDataSetChanged() as it doesn't inspect inside the wrapper object.

NeilS
  • 625
  • 7
  • 13
  • NeilS your answer does seem to make sense, but as I'm new to android, can you provide a sample code for the same. Also can you explain why redrawing listview everytime wont be a costly task performance wise? – Sandeep Choudhary Mar 21 '15 at 14:51
  • You're most of the way there - have a go, I'll help if you get stuck. You've got the Adapter, you've got the ListView (if it's not the parent then it's the parent's parent), you need to know the index/position of the item that's clicked - if you're using ViewHolder pattern then you can get that from the View's tag. You're right it is (slightly) costly, if you want to be more efficient you can call adapter.getView() on the changed row, see here: http://stackoverflow.com/questions/4075975/redraw-a-single-row-in-a-listview/9987616#9987616 – NeilS Mar 21 '15 at 15:13
  • Will try it first thing tomorroe – Sandeep Choudhary Mar 21 '15 at 15:21
  • Hi NeilS, I tried to access adapter in onClick method, but I'm totally lost here. Apologies for my limited android knowledge. I have updated my question with my getView method source code – Sandeep Choudhary Mar 22 '15 at 18:04
  • 1
    You say above that this code is in your 'CustomListViewAdapter' - is this a class that exends ArrayAdapter? If so then you are already 'in' the adapter and you have access to its methods, so in your onClick you can just call getItem(pos) - assuming you can get the pos from your view's tag. getItem() will return you the data item in the list so if you follow my suggestion and use AtomicInteger as your data type ('CustomListViewAdapter extends ArrayAdapter') the item will be an AtomicInteger and you can call .incrementAndGet() on it and then get the system to redraw the view. – NeilS Mar 22 '15 at 23:15
  • Thanks @NeilS for all your time, finally I'm able to fix the issue and publish my app in android market :) – Sandeep Choudhary Mar 26 '15 at 10:16
  • Great! Glad to be of help. – NeilS Mar 26 '15 at 13:53
1

What you're doing is changing only text of textview, not the data you created your adapter with. If you want the described behavoiur, you need to change the data of the adapter and then call notifyDataSetChanged method on the adapter.

Mikhail
  • 3,666
  • 4
  • 30
  • 43
  • But how do I access adapter in onclick method? And ideally I shouldn't be asking for notifyDataSetChanged for every plus minus I do as that will be costly operation. What I should be doing is, updating textview value as well as adapter value, am I right here? – Sandeep Choudhary Mar 21 '15 at 14:14