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

17,556

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.

Share:
17,556

Related videos on Youtube

kaolick
Author by

kaolick

Professional mobile app developer

Updated on June 04, 2022

Comments

  • kaolick
    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 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();
        }
    });
    
  • kaolick
    kaolick about 11 years
    Works like a charm. Thank you so much! So I was only missing swapCursor(cursor);! I added the solution to my post.
  • theblang
    theblang over 10 years
    You mention on a background thread, please. I am curious if you have any thoughts about doing this in AsyncTask vs a Loader?
  • CommonsWare
    CommonsWare over 10 years
    @mattblang: If you happen to have your data served by a ContentProvider for other reasons, use a CursorLoader. Otherwise, just use an AsyncTask, IMHO.
  • Axel
    Axel about 8 years
    @CommonsWare 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.
  • CommonsWare
    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
    Axel about 8 years
    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.