Search code examples
androidlistviewandroid-cursoradapter

Android ViewHolder in CursorAdapter causing listView to get screwed


I've been struggeling in the past few days trying to figure this out, I hope you can help me...

I have an Activity that shows a list of Players by setting a listadapter like this:

PlayerCursorAdapter playerAdapter = new PlayerCursorAdapter(this,
                                         R.layout.players_row, c, columns, to);
setListAdapter(playerAdapter);

When clicking an item in the list, this code will be executed showing a dialog with an "Edit" and "Delete" option for editing and removing players:

private class OnPlayerItemClickListener implements OnItemClickListener {
        public void onItemClick(AdapterView<?> parent, View view, int position,
                long rowId) {
            Toast.makeText(view.getContext(),
                    "Clicked Item [" + position + "], rowId [" + rowId + "]",
                    Toast.LENGTH_SHORT).show();

            // Prepare Dialog with "Edit" and "Delete" option
            final CharSequence[] choices = {
                    view.getContext().getString(R.string.buttonEdit),
                    view.getContext().getString(R.string.buttonDelete) };

            AlertDialog.Builder builder = new AlertDialog.Builder(
                    view.getContext());
            builder.setTitle(R.string.title_edit_delete_player);
            builder.setItems(choices, new EditOrDeleteDialogOnClickListener(
                    view, rowId));

            AlertDialog alert = builder.create();

            // Show Dialog
            alert.show();
        }

Based on your choice (Edit or delete player), the following listener will be executed:

private class EditOrDeleteDialogOnClickListener implements
        DialogInterface.OnClickListener {

    private View view;
    private long rowId;

    public EditOrDeleteDialogOnClickListener(View view, long rowId) {
        this.view = view;
        this.rowId = rowId;
    }

    public void onClick(DialogInterface dialog, int item) {
        if (item == 0) {
            // Edit
            showDialog(PlayGameActivity.DIALOG_EDIT_PLAYER_ID);
        } else if (item == 1) {
            // Delete from database
            DatabaseHelper databaseHelper = new DatabaseHelper(
                    view.getContext());
            databaseHelper.deletePlayer(rowId);

            // Requery to update view.
            ((PlayerCursorAdapter) getListAdapter()).getCursor().requery();

            Toast.makeText(
                    view.getContext(),
                    view.getContext().getString(
                            R.string.message_player_removed)
                            + " " + rowId, Toast.LENGTH_SHORT).show();

        }
    }
}

The code for the adapter is here:

public class PlayerCursorAdapter extends SimpleCursorAdapter {

    private LayoutInflater layoutInflater;
    private int layout;

    public PlayerCursorAdapter(Context context,
            int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        this.layout = layout;
        layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        Cursor c = getCursor();
        View view = layoutInflater.inflate(layout, parent, false);

        // Get Data
        int nameCol = c.getColumnIndex(Player.COLUMN_PLAYER_NAME);
        String name = c.getString(nameCol);
        int gamesPlayedCol = c.getColumnIndex(Player.COLUMN_GAMES_PLAYED);
        String gamesPlayed = c.getString(gamesPlayedCol);
        int gamesWonCol = c.getColumnIndex(Player.COLUMN_GAMES_WON);
        String gamesWon = c.getString(gamesWonCol);

        // Set data on fields
        TextView topText = (TextView) view.findViewById(R.id.topText);
        if (name != null)
            topText.setText(name);

        TextView bottomText = (TextView) view.findViewById(R.id.bottomText);
        if (gamesPlayed != null && gamesWon != null)
            bottomText.setText(view.getContext().getString(
                    R.string.info_played_won)
                    + gamesPlayed + "/" + gamesWon);

        CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);

        // Set up PlayerViewHolder
        PlayerViewHolder playerViewHolder = new PlayerViewHolder();
        playerViewHolder.playerName = name;
        playerViewHolder.gamesPlayed = gamesPlayed;
        playerViewHolder.gamesWon = gamesWon;
        playerViewHolder.isChecked = checkBox.isChecked();
        view.setTag(playerViewHolder);
        return view;
    }

    private class PlayerViewHolder {
        String playerName;
        String gamesPlayed;
        String gamesWon;
        boolean isChecked;
    }

    @Override
    public void bindView(View view, Context context, Cursor c) {
        PlayerViewHolder playerViewHolder = (PlayerViewHolder) view.getTag();

        TextView topText = (TextView) view.findViewById(R.id.topText);
        topText.setText(playerViewHolder.playerName);

        TextView bottomText = (TextView) view.findViewById(R.id.bottomText);
        bottomText.setText(view.getContext()
                .getString(R.string.info_played_won)
                + playerViewHolder.gamesPlayed
                + "/"
                + playerViewHolder.gamesWon);

        CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);
        checkBox.setChecked(playerViewHolder.isChecked);
    }
}

Now, the problem is that after removing a few of the players in the list, the list gets screwed up, eg. it shows something different than what is actually available.

I've experimented a little and if I stop using the PlayerViewHolder in bindView and instead read the text from the cursor and assign it directly to the text fields, then it works.... So question is, why is my ViewHolder screwing up things???

Any help will be greatly appreciated!

Thanks!

  • Zyb3r

Solution

  • Found a solution...

    Basically I reinitialize the Cursor and ListAdapter plus assigns the ListAdapter to the ListView all over again when I change the data in the database.

    I'm not entirely sure why this is nessasary, but notifyDataSetChanged(), notifyDataSetInvalidated() and all the other things I tried didn't work, so now I'm using this approach. :o)

    • Zyb3r