0

UPDATE : I am able to replicate this issue every time on my Galaxy S2 (with and without debugging mode), but never on the Emulator!

I am using a context menu on a ListView (which uses a custom implementation of CursorAdapter) to let the user select the option to 'Delete all'. When this option is selected, all the items displayed in the list are supposed to get deleted permanently from the database, followed by a call to changeCursor(..) on the adapter to force the list to get updated.

What is happening, however, is that even after deleting the records from the database and calling changeCursor(..), the items are visible. Only the item dividers disappear. Only after I touch somewhere on the list, do these items get cleared.

When the user activates the context menu : https://i.stack.imgur.com/ivFvJ.png

After deletion from database AND calling changeCursor(..) : https://i.stack.imgur.com/CX6BM.png

I am having another problem with ListView (Android ListView items overlap while scrolling), and I am using the same ListView, so maybe the problems are related? Is there some step to force the ListView to redraw after a database update? Or is it not happening automatically due to a mistake in how I've implemented the solution? Thanks in advance!

Here's the XML for the ListView

<ListView
        android:id="@+id/all_reminders_list"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:layout_alignParentLeft="true"
        android:clickable="true"
        android:dividerHeight="1.0sp"
        android:animateLayoutChanges="true">

Here's the newView(..) method of my custom CursorAdapter

public View newView(Context context, Cursor cursor, ViewGroup parent) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View view = inflater.inflate(R.layout.view_list_item, parent, false);
    return view;
}

The bindView(..) method of my CursorAdapter

public void bindView(View view, Context context, Cursor cursor) {

        TextView whatTextView = (TextView) view.findViewById(R.id.item_what_text);
        whatTextView.setText(cursor.getString(1));
        TextView whenTextView = (TextView) view.findViewById(R.id.item_when_text);


        if(cursor.getInt(9) != 0) // DONE_FLAG = 1 (completed)
        {
            //Arrow visibility
            ImageView arrow = (ImageView)view.findViewById(R.id.list_item_arrow);
            arrow.setVisibility(View.INVISIBLE);

            //Text color
            whatTextView.setTextColor(Color.LTGRAY);
            whenTextView.setTextColor(Color.LTGRAY);

            //WHEN text
            whenTextView.setText(TimeCalculationHelper.getCompletedTimeString(cursor.getLong(2)));
        }
        else // DONE_FLAG = 0
        {
            //Arrow visibility
            ImageView arrow = (ImageView)view.findViewById(R.id.list_item_arrow);
            arrow.setVisibility(View.VISIBLE);

            //Text color
            whatTextView.setTextColor(Color.BLACK);
            whenTextView.setTextColor(Color.BLACK);

            //WHEN text
            whenTextView.setText(TimeCalculationHelper.getTimeRemainingString(cursor.getLong(2)));


        }
}

Here's my onContextItemSelected(..) method from the Activity that contains the ListView

public boolean onContextItemSelected(MenuItem item)
    {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        ListView allRemindersList = (ListView)findViewById(R.id.all_reminders_list);

        switch (item.getItemId()) {
        case R.id.delete_item:
            //Delete the selected reminder from the database
            databaseHelper.deleteRowByID(info.id);

            //Refresh the main activity list
            ((ActiveRemindersAdapter) allRemindersList.getAdapter()).changeCursor(databaseHelper.getAllRemindersForList());
            return true;

        case R.id.delete_done:
            //Delete all reminders with DONE_FLAG = 1
            databaseHelper.deleteDoneReminders();

            //Refresh the main activity list
            ((ActiveRemindersAdapter) allRemindersList.getAdapter()).changeCursor(databaseHelper.getAllRemindersForList());
        }
        return false;
    }
Community
  • 1
  • 1
Pritin Tyagaraj
  • 93
  • 3
  • 10

1 Answers1

1

Call notifyDataSetChanged() on your adapter after the cursor change to reload the views. And better use a CursorAdapter from SupportLibrary if running pre-honeycomb devices.

Just looked into the code. Better use swapCursor() which automatically registers new content observers and calls notifyDataSetChanged() for you.

From CursorAdapter source code.

/**
 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
 * closed.
 * 
 * @param cursor The new cursor to be used
 */
public void changeCursor(Cursor cursor) {
    Cursor old = swapCursor(cursor);
    if (old != null) {
        old.close();
    }
}

/**
 * Swap in a new Cursor, returning the old Cursor.  Unlike
 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
 * closed.
 *
 * @param newCursor The new cursor to be used.
 * @return Returns the previously set Cursor, or null if there wasa not one.
 * If the given new Cursor is the same instance is the previously set
 * Cursor, null is also returned.
 */
public Cursor swapCursor(Cursor newCursor) {
    if (newCursor == mCursor) {
        return null;
    }
    Cursor oldCursor = mCursor;
    if (oldCursor != null) {
        if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
        if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
    }
    mCursor = newCursor;
    if (newCursor != null) {
        if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
        if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
        mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
        mDataValid = true;
        // notify the observers about the new cursor
        notifyDataSetChanged();
    } else {
        mRowIDColumn = -1;
        mDataValid = false;
        // notify the observers about the lack of a data set
        notifyDataSetInvalidated();
    }
    return oldCursor;
}
Yaroslav Mytkalyk
  • 16,950
  • 10
  • 72
  • 99
  • Thanks for replying. Does `notifyDataSetChanged()` behave any differently from a call to `changeCursor()`? Will check and confirm if it solves my problem. And I am only targetting ICS and newer devices. – Pritin Tyagaraj Feb 07 '13 at 15:58
  • Updated answer. notifyDataSetChanged() is a method of BaseAdapter (from which the CursorAdapter is extended) that forces reloading the views. – Yaroslav Mytkalyk Feb 07 '13 at 16:28
  • Thanks, but I tried calling `notifyDataSetChanged()` after `changeCursor()`. I also tried calling `swapCursor()` instead. I still have the exactly same problem. – Pritin Tyagaraj Feb 08 '13 at 10:56
  • @PritinTyagaraj Do you perform a query after deletion before you change the Cursor? Are you sure the Cursor you set is empty? Log out cursor.getCount() and see how many rows in contains. – Yaroslav Mytkalyk Feb 08 '13 at 11:04
  • Yes, I have ensured the same. The `databaseHelper.getAllRemindersForList()` method actually performs a `SELECT` query and returns a `Cursor` for the result. Another strange thing I've noticed, that I am not able to replicate this in the Emulator, but I am able to replicate it every time on my Galaxy S2 (with and without being in debugging mode) – Pritin Tyagaraj Feb 08 '13 at 11:48
  • Did you try cursor.getCount()? Is et empty or not? If not - then it's now the ListView problem, but DB problem. – Yaroslav Mytkalyk Feb 08 '13 at 11:57
  • Apologies if my question wasn't clear (I'm not allowed to post more than 2 images). Please compare the first screenshot (before deletion - `ListView` has dividers) and the second screenshot (after deletion and `notifyDataSetChange()` - only dividers vanish). After deletion, once I touch anywhere on the list, the deleted items are removed from the screen. It is certainly a `ListView` UI rendering-related issue. – Pritin Tyagaraj Feb 08 '13 at 12:05
  • @PritinTyagaraj I remember some strange rendering with tabs on that device. Try adding android:background="#ffffffff" to your ListView. And after swapping Cursor call invalidate() on ListView. – Yaroslav Mytkalyk Feb 08 '13 at 12:15