Android: How to requery a Cursor to refresh ListView after deleting database row?
Asked Answered
A

1

9

This might be a noob question but I'm quite new to all this SQLite-Database-Cursor-Adapter-ListView-Do-It-Properly-Stuff.

What I have:

In my MainActivity I have a ListView. I use an SQLite database and populate the ListView with a custom adapter extending SimpleCursorAdapter. By clicking on an item in my ActionBar I activate Contextual Action Mode. Everything is working so far.

What I want:

By clicking on a certain icon in my ListView item the according database row should be deleted and the ListView should be refreshed.

My question:

How do I refresh my Cursor and my ListView properly? When I don't use cursor.requery() in my OnClickListener and use cursor = dbm.getIOIOSensorsCursor() instead I get a CursorIndexOutOfBoundsException a few rows below at the line

int state = cursor.getInt(cursor.getColumnIndex(IOIOSensorSchema.STATE));

My app crashes, but after reloading it the database has been deleted and the according ListView item is gone.

I guess the crash must have something to do with _position in get getView method because _position is final. However, when I use cursor.requery() everything works as it should.

But this method is deprecated and it's documentation says "Don't use this...". I'm a friend of coding properly (I'm still a beginner and want to learn to code the right way and not quick-and-dirty) and want to know how to do this right. I don't know if it's important but I'm testing my app only on my (really fast) Nexus 4. There seem to be no problems with refreshing the Cursor fast enough, but I wonder if it will work on slower devices. In case it's important for you my database will contain about 10-20 rows with about 12 columns. I guess this is a really small database.

Here is the relevant code of my custom adapter:

public class IOIOSensorCursorAdapterCam extends SimpleCursorAdapter
{
static class ViewHolder
{
ImageView stateIV, removeIV;
TextView nameTV, pinNumberTV, feedIDTV, freqTV;
}

private Context ctx;
private Cursor cursor;
private IodDatabaseManager dbm;

public IOIOSensorCursorAdapterCam(Context _context, int _layout,
    Cursor _cursor, String[] _from, int[] _to, int _flags)
{
super(_context, _layout, _cursor, _from, _to, _flags);
ctx = _context;
cursor = _cursor;
dbm = new IodDatabaseManager(_context);
}

@Override
public View getView(final int _position, View _convertView,
    ViewGroup _parent)
{
ViewHolder holder = null;

LayoutInflater inflater = (LayoutInflater) ctx
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

// There is no view at this position, we create a new one. In this case
// by inflating an xml layout.
if (_convertView == null)
{
    // Inflate a layout
    _convertView = inflater.inflate(R.layout.listview_item_sensor_cam,
        null);

    holder = new ViewHolder();
    holder.stateIV = (ImageView) _convertView
        .findViewById(R.id.stateImageView);
    holder.nameTV = (TextView) _convertView
        .findViewById(R.id.sensorNameTextView);
    holder.pinNumberTV = (TextView) _convertView
        .findViewById(R.id.sensorPinNumberTextView);
    holder.feedIDTV = (TextView) _convertView
        .findViewById(R.id.sensorFeedIDTextView);
    holder.freqTV = (TextView) _convertView
        .findViewById(R.id.sensorFrequencyTextView);
    holder.removeIV = (ImageView) _convertView
        .findViewById(R.id.removeImageView);
    _convertView.setTag(holder);
}
// We recycle a View that already exists.
else
{
    holder = (ViewHolder) _convertView.getTag();
}

// Set an OnClickListener to the "Delete Icon"
holder.removeIV.setOnClickListener(new OnClickListener()
{
    @SuppressWarnings("deprecation")
    @Override
    public void onClick(View _view)
    {
    cursor.moveToPosition(_position);

    // Delete sensor from database here
    int sensorID = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.SENSOR_ID));
    dbm.deleteIOIOSensor(sensorID);

    // This leads to a "CursorIndexOutOfBoundsException" and cannot
    // be used to refresh the ListView
//      cursor = dbm.getIOIOSensorsCursor();

    // Refresh ListView
    cursor.requery();
    notifyDataSetChanged();
    }
});

cursor.moveToPosition(_position);

if (cursor.getCount() > 0)
{
    int state = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.STATE));

    if (state == 0)
    {
    holder.stateIV.setImageResource(R.drawable.av_play_over_video);
    holder.stateIV.setColorFilter(ctx.getResources().getColor(
        R.color.hint_lighter_gray));
    // _convertView.setAlpha((float) 0.5);
    holder.nameTV.setTextColor(ctx.getResources().getColor(
        R.color.hint_darker_gray));
    }
    else
    {
    holder.stateIV.setImageResource(R.drawable.av_pause_over_video);
    holder.stateIV.setColorFilter(ctx.getResources().getColor(
        android.R.color.holo_green_light));
    // _convertView.setAlpha((float) 1);
    holder.nameTV.setTextColor(ctx.getResources().getColor(
        android.R.color.black));
    }

    // Set the sensor's name to the according TextView
    String sensorName = cursor.getString(cursor
        .getColumnIndex(IOIOSensorSchema.NAME));
    holder.nameTV.setText(sensorName);

    // Set the sensor's pin number to the according TextView
    int pinNumber = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.PIN_NUMBER));
    holder.pinNumberTV.setText("" + pinNumber);

    // Set the sensor's feed ID to the according TextView
    int feedID = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.FEED_ID));
    holder.feedIDTV.setText("" + feedID);

    // Set the sensor's frequency to the according TextView
    int frequency = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.FREQUENCY));
    int timeUnit = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.TIME_UNIT));
    String frequencyTextViewText = "";
    switch (timeUnit)
    {
    case IodIOIOSensor.TIME_UNIT_MINUTES:
    frequencyTextViewText = frequency + " min";
    break;
    case IodIOIOSensor.TIME_UNIT_HOURS:
    frequencyTextViewText = frequency + " h";
    break;
    default:
    frequencyTextViewText = frequency + " sec";
    break;
    }
    holder.freqTV.setText(frequencyTextViewText);
}
return _convertView;
}
}

Edit:

Here is my relevant code from the OnCickListener after implementing the solution:

// Set an OnClickListener to the "Delete Icon"
holder.removeIV.setOnClickListener(new OnClickListener()
{
    @Override
    public void onClick(View _view)
    {
    cursor.moveToPosition(_position);

    // Delete sensor from database here
    int sensorID = cursor.getInt(cursor
        .getColumnIndex(IOIOSensorSchema.SENSOR_ID));
    dbm.deleteIOIOSensor(sensorID);

    Toast.makeText(ctx, R.string.toast_sensor_deleted,
        Toast.LENGTH_SHORT).show();

    // Refresh ListView
    cursor = dbm.getIOIOSensorsCursor();
    swapCursor(cursor);

    notifyDataSetChanged();
    }
});
Anytime answered 5/6, 2013 at 10:43 Comment(0)
D
15

How do I refresh my Cursor and my ListView properly?

You "refresh [your] Cursor" by running your code again to get the Cursor, using the code you used to create the original Cursor (on a background thread, please). You refresh your ListView by calling changeCursor() or swapCursor() on the CursorAdapter.

Dextran answered 5/6, 2013 at 11:4 Comment(6)
Works like a charm. Thank you so much! So I was only missing swapCursor(cursor);! I added the solution to my post.Anytime
You mention on a background thread, please. I am curious if you have any thoughts about doing this in AsyncTask vs a Loader?Haema
@mattblang: If you happen to have your data served by a ContentProvider for other reasons, use a CursorLoader. Otherwise, just use an AsyncTask, IMHO.Dextran
@Dextran What should happen when CursorLoader calls swapCursor(cursor) and one or more items in a ListView are selected and the CAB is activated? Should I finish the CAB? I might be wrong, but I think users would think it is a bit odd that whatever item they selected, gets deselected out of the blue because a ContentProvider detected some changes in the database.Herculaneum
@Axel: That's one of the reasons that I don't use the Loader framework. IMHO, only update the UI when the user is not in the middle of an operation.Dextran
Thank you @CommonsWare. This confirms what I've seen in some apps, including the Twitter app, where whenever there's new data to be loaded, it doesn't get loaded automatically, but the app let the user know there's new content. Whether the users chooses to load it or not, it's up to them. Once again, thank you.Herculaneum

© 2022 - 2024 — McMap. All rights reserved.