mercredi 22 février 2017

Listview checkbox repeating in recycled views

First of all, I know this issue is common on SO and elsewhere but I have spent hours trying to understand but cannot because the code is either not all there, the answer is too specific to the asker's implementation or they use another kind of adapter.

I have a custom ArrayAdapter that is attached to a ListView holding an ArrayList of SimplePlaces. SimplePlaces is a class I made that has a boolean with a getter and setter for whether that item is checked in the list. By default this would always be false.

The activity overrides the OnCheckChanged() method for the listener. When this is called I get the SimplePlace item from the list and call its setter, setChecked() to match boolean passed with the listener. Then I call notifyDataSetChanged() on the adapter.

Then in the ArrayAdapter I set the checkbox according to the getter for the SimplePlace item but this makes my app crash which happens on the line that says: int position = mListview.getPositionForView(buttonView); inside onCheckedChanged() in the activity. The exception is: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Object.equals(java.lang.Object)' on a null object reference

If I do not try to set the checkbox then the app does not crash but checking an item repeats every 6 places in the listview, something to do with the recycling of views but I don't understand how exactly.

Please can someone explain what is happening here and how I modify my code to get this to work.

Activity to display the list

public class ChoosePlacesActivity extends AppCompatActivity
        implements CompoundButton.OnCheckedChangeListener {
    private final String LOG_TAG = Utility.APP_TAG +
            ChoosePlacesActivity.class.getSimpleName();

    ArrayList<SimplePlace> mSimplePlaces;
    ListView mListview;
    PlaceAdapter mAdapter;

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        Log.d(LOG_TAG, "onCheckedChanged");
        int position = mListview.getPositionForView(buttonView);
        if (position != ListView.INVALID_POSITION) {

            SimplePlace place = mSimplePlaces.get(position);
            place.setChecked(isChecked);
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(LOG_TAG, "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_choose_places);

        // Get intent data
        Intent intent = getIntent();
        mSimplePlaces = (ArrayList<SimplePlace>)
                intent.getSerializableExtra(Utility.PLACES_LIST);

        // Find the list view and set up the adapter
        mListview = (ListView) findViewById(R.id.listview_choose_places);
        mAdapter = new PlaceAdapter(this, mSimplePlaces);
        mListview.setAdapter(mAdapter);
        mAdapter.addAll(mSimplePlaces);
    }
}

Custom ArrayAdapter

public class PlaceAdapter extends ArrayAdapter<SimplePlace> {

    private final String LOG_TAG = Utility.APP_TAG + PlaceAdapter.class.getSimpleName();
    private Context context;

    public PlaceAdapter(Context context, List<SimplePlace> places) {
        super(context, R.layout.list_item_chooseplaces, places);
        Log.d(LOG_TAG, "PlaceAdapter constructor");
        this.context = context;
    }

    // Keep a reference to the view to avoid repeatedly calling findViewById()
    private static class SimplePlaceHolder {
        TextView name;
        TextView type;
        TextView distance;
        CheckBox checkbox;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Log.d(LOG_TAG, "getView");
        SimplePlaceHolder holder = new SimplePlaceHolder();

        // Inflate a new view, else recycle existing view
        if (convertView == null) {
            // Inflate a new view
            LayoutInflater inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.list_item_chooseplaces, parent, false);

            // Find the views
            holder.name = (TextView) convertView.findViewById(R.id.place_name);
            holder.type = (TextView) convertView.findViewById(R.id.place_type);
            holder.distance = (TextView) convertView.findViewById(R.id.place_distance);
            holder.checkbox = (CheckBox) convertView.findViewById(R.id.place_checkbox);
            convertView.setTag(holder);

            // Click listener for list item
            holder.checkbox.setOnCheckedChangeListener((ChoosePlacesActivity) context);
        }
        else {
           // Get recycled ViewHolder object from tag
           holder = (SimplePlaceHolder) convertView.getTag();
        }

        // Get the data item associated with the specified position in the data set
        // and populate its values
        SimplePlace place = getItem(position);
        holder.name.setText(place.getName());
        holder.type.setText(place.getType());
        holder.distance.setText(String.valueOf(place.getDistance()) );
        holder.checkbox.setChecked(place.isChecked() );

        return convertView;
    }
}




Aucun commentaire:

Enregistrer un commentaire