Search code examples
androidlistviewcustom-adapternotifydatasetchanged

Delete items from custom adapter android


I'm having trouble to make a multiple deletion works on a ListView in android.

1) I've created a CustomAdapter to customize each row with a few buttons and texts (I'll put here only what I think its most important):

public class PlayableCustomAdapter extends ArrayAdapter<String> {
private final boolean[] checkedItems;
private final LayoutInflater mInflater;
private final ActivityCallback activityCallback;

public PlayableCustomAdapter(Context context, ActivityCallback activityCallback,
                             List<String> files) {
    super(context, NO_SELECTION);

    this.activityCallback = activityCallback;
    this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.checkedItems = new boolean[files.size()];

    addAll(files);
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    View row = convertView;
    Holder holder;

    if (row == null) {
        row = mInflater.inflate(R.layout.list_view_row_song, null);

        holder = new Holder();
        holder.text = (TextView) row.findViewById(R.id.tvFileName);
        holder.configs = (TextView) row.findViewById(R.id.tvFileConfiguration);
        holder.checkBox = (CheckBox) row.findViewById(R.id.cbDelete);
        holder.playButton = (ImageButton) row.findViewById(R.id.btnPlay);
        holder.settingsButton = (ImageButton) row.findViewById(R.id.btnSettings);
        holder.text.setText(getItem(position));
        holder.configs.setText(getLyricConfiguration(getItem(position)));
        row.setTag(holder);

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

    holder.playButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startPrompter(position);
        }
    });

    holder.settingsButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startSettings(position);
        }
    });

    holder.text.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startEditFile(position);
        }
    });
    holder.checkBox.setChecked(checkedItems[position]);
    holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                checkedItems[position] = true;
                activityCallback.showContent();
            } else {
                verifySelectedItems(position);
            }
        }
    });

    return row;
}

private void verifySelectedItems(int position) {
    checkedItems[position] = false;
    for (boolean checked : checkedItems) {
        if (checked)
            return;
    }
    activityCallback.hideContent();
}

public boolean isChecked(int position) {
    return checkedItems[position];
}

@Override
public void remove(String s) {
    verifySelectedItems(getPosition(s));
    super.remove(s);
    notifyDataSetChanged();
}

static class Holder {
    TextView text;
    TextView configs;
    ImageButton playButton;
    CheckBox checkBox;
    ImageButton settingsButton;
}

2) I've used this adapter in a Activity and everything works like a charm, except from the multiple deletion. Only when I clear all the adapter items the listview is updated correctly. When I delete one or more items, the adapter seems to be fine. All the positions are passed correctly. But I don't know how, only the items in the end of the list are removed from the view.

3) As you can see, I'm using notifyDataSetChanged(); after deletion and the problem still happens. The only (and HORRIBLE) solution that I could make it work was recreating the Activity after deletion (recreate();). The screen blinks, its awful, but it's fully functional. And now that I have some free time I am asking you guys for some help.

Here is the code from the activity that uses the adapter:

private void deleteFilesFromDisk(List<Integer> positionsToDelete, ArrayAdapter adapter) {
    File[] files = getAppFiles();
    for (int position : positionsToDelete) {
        if (files[position].delete()) {
            adapter.remove(fileNames.get(position));                
        } else
            showMessage(R.string.delete_file_error, fileNames.get(position));
    }
    recreate(); // Horrible workaround, but it works!
}

Please remember that this method is only a example. Especially the last line. Without the recreation command, only the items in the end of the list are removed (at least visually speaking, from the UI).

Any sugestions?


Solution

  • Your problem might be here

    @Override
    public void remove(String s) {
        verifySelectedItems(getPosition(s));
        super.remove(s);
        notifyDataSetChanged();
    }
    

    You remove the item from the adapter, but not the index from checkedItems If i were you, instead of keeping a boolean array to keep track of checked items, I would use an ArrayList<Integer> and keep all of the checked positions in the list.

    You could then change that method to look something like this

    @Override
    public void remove(String s) {
        int pos = getPosition(s);
        verifySelectedItems(pos);
        remove(s);
        checkedItems.remove(Integer.valueOf(pos));
        notifyDataSetChanged();
    }
    

    And then this...

    private void verifySelectedItems(int position) {
        checkedItems[position] = false;
        for (boolean checked : checkedItems) {
            if (checked)
                return;
        }
        activityCallback.hideContent();
    }
    

    could instead be this

    private void verifySelectedItems(int position) {
        if(!checkedItems.contains(position)) {
            activityCallback.hideContent();
        }
    }
    

    EDIT:

    Sorry I didn't read the question properly, I think this is your problem

    if (row == null) {
            row = mInflater.inflate(R.layout.list_view_row_song, null);
    
            holder = new Holder();
            holder.text = (TextView) row.findViewById(R.id.tvFileName);
            holder.configs = (TextView) row.findViewById(R.id.tvFileConfiguration);
            holder.checkBox = (CheckBox) row.findViewById(R.id.cbDelete);
            holder.playButton = (ImageButton) row.findViewById(R.id.btnPlay);
            holder.settingsButton = (ImageButton) row.findViewById(R.id.btnSettings);
            holder.text.setText(getItem(position));
            holder.configs.setText(getLyricConfiguration(getItem(position)));
            row.setTag(holder);
    
        } else {
            holder = (Holder) convertView.getTag();
        }
    

    You are removing the correct items, but this prevents the views from being updated after a notifydatasetchanged, maybe try something like this

    if(convertView == null) {
        convertView = mInflater.inflate(R.layout.list_view_row_song, null);
    }
    
    Holder holder = new Holder();   
    holder.text = (TextView) convertView.findViewById(R.id.tvFileName);
    holder.configs = (TextView) convertView.findViewById(R.id.tvFileConfiguration);
    holder.checkBox = (CheckBox) convertView.findViewById(R.id.cbDelete);
    holder.playButton = (ImageButton) convertView.findViewById(R.id.btnPlay);
    holder.settingsButton = (ImageButton) convertView.findViewById(R.id.btnSettings);
    
    holder.text.setText(getItem(position));
    holder.configs.setText(getLyricConfiguration(getItem(position)));