0

This is regarding the recycling issue. I am using a custom adapter to populate the list view. In the custom row there is an image view, two text boxes and a check box. The all the elements get populated but the check box is not populated correctly.

Inside the getView() I perform a condition and if the condition is true I set the check box to enable state. This works fine but with the correct check box which is ticked, there are some other check boxes getting ticked as well. I went through many stack overflow similar questions but was unable to find an answer. Any help is greatly appreciated.

Below is my adapter class:

public class LocationsListAdapter extends BaseAdapter {

    List<Locations> data;
    Context context;
    Locations userSelectedLocation;
    private SharedPreferences locationPreferences;
    private SharedPreferences.Editor locationPrefsEditor;

    public LocationsListAdapter(List<Locations> data, Context c) {
        this.data = data;
        this.context = c;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @SuppressWarnings("static-access")
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {


        ViewHolder holder = null;
        Log.v("ConvertView", String.valueOf(position));

        if (convertView == null) {
            LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(R.layout.find_your_location_row, null);

            holder = new ViewHolder();
            holder.LocationImage = (SmartImageView) convertView.findViewById(R.id.loca_row_image);
            holder.locationName = (TextView) convertView.findViewById(R.id.txt_loca_name);
            holder.LocationDescription = (TextView) convertView.findViewById(R.id.txt_loca_desc);
            holder.locationCheckText = (TextView) convertView.findViewById(R.id.txt_check);
            holder.locationCheck = (CheckBox) convertView.findViewById(R.id.location_check);
            holder.locationCheck.setOnCheckedChangeListener(myCheckChangList);
            convertView.setTag(holder);

            locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
            locationPrefsEditor = locationPreferences.edit();
            String locationID  = locationPreferences.getString("locationID", "");

            try {
                if(locationID.contains(String.valueOf(data.get(position).getLocationID()))){
                    holder.locationCheck.setChecked(true);
                }
            } catch (Exception e) {
                Log.e("Fatal", " Exception");
            }


             holder.locationCheck.setOnClickListener( new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        userSelectedLocation = data.get(position);
                        locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
                        locationPrefsEditor = locationPreferences.edit();
                        String userSelectedLocationID = userSelectedLocation.getLocationID();
                        locationPrefsEditor.clear();
                        locationPrefsEditor.putString("locationID", userSelectedLocationID);
                        locationPrefsEditor.commit();
                        Intent intent = new Intent(context, HomeScreen.class);
                        context.startActivity(intent);
                        Log.e("Check Box ", "Clicked");
                    }
            });

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

        final Locations location = data.get(position);

        holder.LocationImage.setImageUrl(location.getImagePath());
        holder.locationName.setText(location.getLocationName());
        holder.LocationDescription.setText(location.getLocationDescription());

        return convertView;
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        if (observer != null) {
            super.unregisterDataSetObserver(observer);
        }
    }

    protected class ViewHolder {
        protected SmartImageView LocationImage;
        protected TextView locationName;
        protected TextView LocationDescription;
        protected TextView locationCheckText;
        protected CheckBox locationCheck ;
    }

    OnCheckedChangeListener myCheckChangList = new OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
            Log.e("checked", "");
        }
    };
}
Nitin Misra
  • 4,472
  • 3
  • 34
  • 52
TharakaNirmana
  • 10,237
  • 8
  • 50
  • 69
  • possible duplicate of [Checkbox not working properly with ListView](http://stackoverflow.com/questions/12085661/checkbox-not-working-properly-with-listview) – Braj Dec 02 '13 at 08:22

6 Answers6

1

You need to save CheckBox state in an Array or something similar when they are checked/un-checked in order to remember state and at the end of the getView(...) method, need to set state for CheckBox depending on array values.

Have a look Checkbox not working properly with ListView link for more details.

Community
  • 1
  • 1
Braj
  • 2,164
  • 2
  • 26
  • 44
1

You have to do something like this at start make an array list and store state as false

 private ArrayList<Boolean> itemChecked = new ArrayList<Boolean>();    

  for (int i = 0; i < this.getCount(); i++) {
            itemChecked.add(i, false); // initializes all items value with false
        }

and whenever you click just change the state and populate the list with updated values everytime.

keshav
  • 3,235
  • 1
  • 16
  • 22
1

OnClickListener() is being declared only for the first couple of list items, and is using the same "position" for all the rest of the items.

One option that can work is that you can set the position in the tag of each checkbox in the list and get it in your OnClickListener from the v param

asaf gitai
  • 400
  • 2
  • 10
1
locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
    locationPrefsEditor = locationPreferences.edit();
    String locationID  = locationPreferences.getString("locationID", "");

    try {
        if(locationID.contains(String.valueOf(data.get(position).getLocationID()))){
            holder.locationCheck.setChecked(true);
        }
    } catch (Exception e) {
        Log.e("Fatal", " Exception");
    }

This part of code is only executed, if your convertView == null. You are not doing setChecked when your convertView != null, that means when you reuse view, it will not be checked correctly

hendrix
  • 3,364
  • 8
  • 31
  • 46
1

I believe your problem lies in your if/else statement.

If convertView is null then you create a new view and populate it with all the correct data but if it is not null (i.e it is a recycled view), you simply return the exact same view back from the tag. You do not do anything with the view regarding setting its properties so it maintains the state it already had (some of which were checked)

Set your properties after you have a valid viewHolder.

You currently have this model.

if (convertView == null) {
    //Your code to create the view
    // Your code to set the view properties
}else {
    holder = (ViewHolder) convertView.getTag();
}

Change it to this (move the code that sets the view properties outside the if/else so that the view properties can be set for all views regardless of whether they are recycled.

 if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.find_your_location_row, null);

        holder = new ViewHolder();
        holder.LocationImage = (SmartImageView) convertView.findViewById(R.id.loca_row_image);
        holder.locationName = (TextView) convertView.findViewById(R.id.txt_loca_name);
        holder.LocationDescription = (TextView) convertView.findViewById(R.id.txt_loca_desc);
        holder.locationCheckText = (TextView) convertView.findViewById(R.id.txt_check);
        holder.locationCheck = (CheckBox) convertView.findViewById(R.id.location_check);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    Locations location = data.get(position);

    holder.LocationImage.setImageUrl(location.getImagePath());
    holder.locationName.setText(location.getLocationName());
    holder.LocationDescription.setText(location.getLocationDescription());
    boolean ShouldBoxBeChecked = //Insert check for the current box here
    holder.locationCheck.setChecked(ShouldBoxBeChecked);
Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • I tried this now. Still no luck. SOme times behaves as expected but most of the time does not. Three or two check boxes get selected at times with the correct row. Any ideas? – TharakaNirmana Dec 02 '13 at 07:33
  • Your code that sets the checkbox state is 1) conditional. 2) only applies to new views not recycled ones. Set the check state before you return convertView at the end of the method. Note that your code only ever sets the state of a checkbox to true. There is nothing that sets it to false if the recycled view is already checked but does not need to be. – Kuffs Dec 02 '13 at 07:55
  • locationCheck.setOnClickListener is also only set for new views but it hard codes the position into the method. This means that any recycled views will be using the old position value and not the current one. – Kuffs Dec 02 '13 at 08:10
  • I guess you are telling me to do this kind of thing: if (convertView == null) { LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.find_your_location_row, null); }else { holder = (ViewHolder) convertView.getTag(); } and then put whatever that was after inflating below the else. I did so, but still no luck.. – TharakaNirmana Dec 02 '13 at 08:15
  • without seeing your updated code, I cannot tell you what you are doing wrong. I have posted a simplified version of how your code needs to be. – Kuffs Dec 02 '13 at 08:20
  • without seeing your updated code, I cannot tell you what you are doing wrong. I have been meaning to start up a blog with some tips for beginners. Your question inspired my first post. http://android.kuffs.co.uk/2013/12/custom-list-adapter-with-icon-and.html – Kuffs Dec 02 '13 at 11:50
  • This certainly helped me: http://android.kuffs.co.uk/2013/12/custom-list-adapter-with-icon-and.html Took me about 2 hours but now works perfectly! – TharakaNirmana Dec 03 '13 at 04:17
1

Here , i have a solution for you.

Basically i have created two array list of type string. When you check a checkbox, the corresponding textview value ( or whatever value you can store correspoding to that listview item) is stored in it the list CHECKEDLIST. In the other list ALLVALUES, all the values are stored.

On button click i match both the lists and if a match occurs, then i remove that value from ALLVALUES and call notifydatasetchanged

package com.example.test;

import java.io.File;
import java.util.ArrayList;


import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class MainActivity extends Activity implements OnClickListener {
    private ArrayList<String> checkedIndices = new ArrayList<String>();
    ArrayList<String> list = new ArrayList<String>();
    AScustomadapter adapter;
int count = 0;
private String[] vidNames = {"a","b","c","d","e","f","g","h","i"};
private ListView myList;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button b1 = (Button) findViewById(R.id.button1);
    b1.setOnClickListener(this);



    for (int i = 0; i < vidNames.length; i++) {
        list.add(vidNames[i]);
    }

    myList = (ListView)findViewById(R.id.list);
    adapter = new AScustomadapter(getApplicationContext(), list);
    myList.setAdapter(adapter);
}

@Override
public void onClick(View v) {
    if(v.getId() == R.id.button1) {


        for (int i = 0; i < checkedIndices.size(); i++) {
            for(int j = 0; j <list.size();j++){
            if(list.get(j).contains(checkedIndices.get(i))){
                list.remove(j);
            }
            }
        }
        adapter.notifyDataSetChanged();



    } 

}

public class AScustomadapter extends BaseAdapter {

private ArrayList<String> mListItems;
private LayoutInflater mLayoutInflater;
int i = 0;

public AScustomadapter(Context context, ArrayList<String> arrayList) {
    mListItems = arrayList;
    //get the layout inflater
    mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public int getCount() {
    return mListItems.size();
}

@Override
public Object getItem(int i) {
    return list.get(i);
}

@Override
public long getItemId(int i) {
    return 0;
}

public int getTotalCheckedCount() {
    return checkedIndices.size();
}

@Override
public View getView(final int position, View view, ViewGroup viewGroup) {
    ViewHolder holder;

    if (view == null) {
        holder = new ViewHolder();

        view = mLayoutInflater.inflate(R.layout.list_item, null);
        holder.itemName = (TextView) view.findViewById(R.id.list_item_text_view);
        holder.cb1 = (CheckBox) view.findViewById(R.id.checkBox1);

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

    final String stringItem = mListItems.get(position);

    if (stringItem != null) {
        if (holder.itemName != null) {
            holder.itemName.setText(stringItem);
        }
    }

    holder.cb1.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
                //is chkIos checked?
        if (((CheckBox) v).isChecked()) {
            if(!checkedIndices.contains(getItem(index).toString()))
                checkedIndices.add(getItem(index).toString());          }
        else {
            checkedIndices.remove(getItem(index).toString());

        }
          //case 2

      }
    });

    if(checkedIndices.contains((Integer)position)) {
        holder.cb1.setChecked(true);
    } else {
        holder.cb1.setChecked(false);
    }

    //this method must return the view corresponding to the data at the specified position.
    return view;
}

private class ViewHolder {

    protected TextView itemName;
    protected CheckBox cb1;

}
  }


 }
Rahul Gupta
  • 5,275
  • 8
  • 35
  • 66