1

I'm attempting to implement a multiple choice ListView with checkboxes, and I've defined a custom adapter class, and it's as follows:

package com.ameer.easycooking;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class CategoryAdapter extends ArrayAdapter<String> {

    private List<String> categoryList;
    private Context context;
    private List<String> selected = new ArrayList<>();

    public CategoryAdapter(List<String> categoryList, Context context) {
        super(context, R.layout.category_list_item, categoryList);
        this.categoryList = categoryList;
        this.context = context;
    }

    public static class CategoryHolder {
        private CheckBox checkBox;
        private TextView categoryName;

        public CategoryHolder(CheckBox checkBox, TextView categoryName) {
            this.checkBox = checkBox;
            this.categoryName = categoryName;
        }

        public CheckBox getCheckBox() {
            return checkBox;
        }

        public void setCheckBox(CheckBox checkBox) {
            this.checkBox = checkBox;
        }

        public TextView getCategoryName() {
            return categoryName;
        }

        public void setCategoryName(TextView categoryName) {
            this.categoryName = categoryName;
        }
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;

        CategoryHolder holder = new CategoryHolder((CheckBox) v.findViewById(R.id.checkBox), (TextView) v.findViewById(R.id.name));

        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(R.layout.category_list_item, null);

            holder.getCheckBox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        if (isChecked) {
                            selected.add(holder.getCheckBox().getText().toString());
                        }else{
                            selected.remove(holder.getCheckBox().getText().toString());
                        }
                }
            });
            v.setTag(holder);
        } else {
            holder = (CategoryHolder) v.getTag();
        }

        String category = categoryList.get(position);
        holder.categoryName.setText(category);
        holder.checkBox.setTag(category);

        return v;
    }

    public List<String> getSelected() {
        return selected;
    }
}

I would like to store checked items in the selected List and then retrieve them with the getter at the end of the class, but It's telling me that 'holder' needs to be declared final, and if I do declare it final I can no longer assign a value to it using holder = (CategoryHolder) v.getTag(); So solving one problem is creating another one. :( How can I solve both?

Thanks in advance.

user3501779
  • 157
  • 1
  • 10
  • Your holder is an inner class. You need to declare it as a global variable, then initialize it when you require it. – Pztar Oct 11 '16 at 14:20

2 Answers2

2

You can get the necessary information from the parameters of the OnCheckedChangeListener: buttonView is the checked element. So all you need to do is the following:

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
       CheckBox checkbox;
       if (buttonView instanceof CheckBox )
       {
           checkbox = (CheckBox)buttonView;
       }
       else return;

       if (isChecked) {
            selected.add(checkbox.getText().toString());
       }else{
            selected.remove(checkbox.getText().toString());
       }
}
Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
  • I changed my code to be as you suggested, but now I'm getting a null pointer exception at `CategoryHolder holder = new CategoryHolder((CheckBox) v.findViewById(R.id.checkBox), (TextView) v.findViewById(R.id.name));` Why's that? – user3501779 Oct 11 '16 at 14:50
  • @user3501779 - Glad to hear my answer got you one step further: at least now it's running... somehow ;-) You're getting a NPE because `convertView` may be `null` sometimes. So if you assign `null` to `v`, then `v.findViewById()` causes a NPE. – Bö macht Blau Oct 11 '16 at 14:54
  • Following this info, I move the above line into the `if (convertView == null)` condition, and it's working now and displaying the items in the ListView. However, I'm always getting an empty string in `selected`. Does the `getText()` method return CheckBox text or TextView text? I'm thinking that's where the problem could be, since I'm not assigning anything to CheckBox text, but rather to the corresponding TextView. – user3501779 Oct 11 '16 at 15:12
  • Solved it. I should have used `setText()` on the CheckBoxes rather than `setTag()`, so now I can also omit the TextViews from the adapter entirely. Thank you so much! – user3501779 Oct 11 '16 at 15:21
  • @user3501779 - You're welcome :) Just keep in mind you also could retrieve the category from the CheckBox (even without casting) by using `View.getTag().toString()` Tags can be quite handy sometimes – Bö macht Blau Oct 11 '16 at 15:30
0

If you use object to inner class you should declare it final. It is explained here: Why Java inner classes require "final" outer instance variables?

So, you can make that variable final:

final CategoryHolder holder = convertView != null ? 
    (CategoryHolder) v.getTag() :
    new CategoryHolder((CheckBox) v.findViewById(R.id.checkBox), (TextView) v.findViewById(R.id.name));
Community
  • 1
  • 1
babay
  • 4,689
  • 1
  • 26
  • 40