Android: How to requery a Cursor to refresh ListView after deleting database row?
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
.
Related videos on Youtube
Comments
-
kaolick about 2 years
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 aListView
. I use anSQLite database
and populate theListView
with a custom adapter extendingSimpleCursorAdapter
. By clicking on an item in myActionBar
I activateContextual 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 theListView
should be refreshed.My question:
How do I refresh my
Cursor
and myListView
properly? When I don't usecursor.requery()
in myOnClickListener
and usecursor = dbm.getIOIOSensorsCursor()
instead I get aCursorIndexOutOfBoundsException
a few rows below at the lineint 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 getgetView
method because_position
isfinal
. However, when I usecursor.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(); } });
-
kaolick about 11 yearsWorks like a charm. Thank you so much! So I was only missing
swapCursor(cursor);
! I added the solution to my post. -
theblang over 10 yearsYou mention
on a background thread, please
. I am curious if you have any thoughts about doing this inAsyncTask
vs aLoader
? -
CommonsWare over 10 years@mattblang: If you happen to have your data served by a
ContentProvider
for other reasons, use aCursorLoader
. Otherwise, just use anAsyncTask
, IMHO. -
Axel about 8 years@CommonsWare What should happen when
CursorLoader
callsswapCursor(cursor)
and one or more items in aListView
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 aContentProvider
detected some changes in the database. -
CommonsWare about 8 years@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. -
Axel about 8 yearsThank 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.