Skip to content
This repository was archived by the owner on Apr 26, 2023. It is now read-only.
This repository was archived by the owner on Apr 26, 2023. It is now read-only.

View flicker issue with CursorAdapter #10

Open

Description

@hsanson

When using a cursor adapter with EnhancedListView after I swipe to dismiss the swiped view reappears momentarily and then disappears creating a very ugly flickering effect.

After looking on StackOverflow it looks this is a known issue with CursorAdapters and animated list views such as EnhancedListView. The issue seems to be that the animation finishes and redraws the list view before the item is actually deleted from the adapter. This causes the item being swiped out to reappear on the list and latter when the CursorAdapter finishes updating with the item removed the list is again redrawn without the removed item. This results in noticeable flickering.

I tried several solutions proposed (see links below) but they did not work with the EnhancedListView. The last solution I tried was to reload the cursor without the deleted item inside the dismiss callback but the flickering is still there.

I am open to suggestions on how to work around this issue.

Activity

  1. hsanson commented on Oct 2, 2013

    @hsanson
    Author

    I got a simple (and ugly) work around to the issue. In the onDismiss callback I create a new cursor with the list data but that excludes the item being deleted:

    public Undoable onDismiss(EnhancedListView list, int pos) {
      long id = mAdapter.getItemId(pos);
      String selection = "_id <> " + id;
      mTempCursor = getContentResolver(URI, COLUMNS,  selection. ....);
      mAdapter.swapCursor(mTempCursor);
    
       // after this then proceed to delete the item from the db
       getContentResolver().delete(.....);
    }
    

    Make sure to close the cursor when we get the real cursor with the item already deleted

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
      mAdapter.swapCursor(cursor);
      if(mTempCursor != null) {
        mTempCursor.close();
        mTempCursor = null;
      }
    }
    

    Loading the cursor on the onDismiss callback may beat the purpose of using a loader in the first place and may potentially present a performance issue. If performance is an issue for you then using the ProxyCursor approach (see links on previous comment) may be better. This if you are willing to write and override all methods needed to implement the proxy cursor class.

  2. timroes commented on Oct 2, 2013

    @timroes
    Owner

    Thanks for your report. As you mentioned the swapping adapters might not be the finest solution.

    I explained here why I cannot fix this issue.

    Still I am thinking about good workaround solutions. If I find some free time I will dig a bit into the CursorAdapter and perhaps I will find a solution to add a wrapper (or something similar) to the library, that blocks until deletion has finished.

  3. edoardotognoni commented on Mar 25, 2014

    @edoardotognoni

    Hi guys. Same problem here. Did you find a workaround for that?

  4. draskosaric commented on Mar 25, 2014

    @draskosaric

    Hi, I solved it by creating my own adapter which extends BaseAdapter and it has two private members List and Stack. List contains elements provided by given Cursor and Stack excluded elements. So, when you want to delete ClassA from list, you just push it on stack, remove from list and call notifyDataSetChanged(), and when you want to do undo, you just pull it from stack ,put it in list again in its old position and call notifyDataSetChanged() again. You really delete those object in onLoaderReset() by deliberetly calling doRealDelete() in your adapter, which pulls elements from stack one by one and do bulk delete through your ContentProvider class. You obviuosly won't delete them one by one... This approach probably has its flaws but it is doing well in my app.

  5. edoardotognoni commented on Mar 26, 2014

    @edoardotognoni

    Another solution could be to convert your cursor adapter to an ArrayList adapter or Base adapter. When onLoadFinished is called, move all cursor items into your ListView adapter. In this way you solve the problem. But as all of other ways, there could be performance issues, because first you have to iterate all cursor items, pass them to the list adapter which will iterate again through all of them. I think this is the simpliest solution but in my case would result in a huge code review.

  6. draskosaric commented on Mar 26, 2014

    @draskosaric

    If you find better solution, please post it here, since I am looking for better solution myself.

  7. edoardotognoni commented on Mar 26, 2014

    @edoardotognoni

    I decided to go for an ArrayAdapter. I'm using 20 records in the worst case, so performances wouldn't be a problem. Please keep me updated if you find better solutions

  8. Darclight commented on Apr 2, 2014

    @Darclight

    Did anyone find a better solution for this issue?

  9. Yougin commented on Apr 18, 2014

    @Yougin

    Finally I moved to ArrayAdapter in onLoadFinished usage as edoardotognoni already told. I believe there could be a performance issue, but it works in my concrete case. Not a solution but workaround of course.

  10. micHar commented on Apr 30, 2014

    @micHar

    @timroes maybe a workaround would be to introduce a small delay after the collapse animation. Let's say a method like setDelayAfterCollapse(int delayInMiliseconds). This way the collapse animation would be followed by this delay and adapter would have time to swap the Cursor.

  11. v3nko commented on May 5, 2014

    @v3nko

    I've made an implementation of base cursor adapter class that can be used as workaround for this problem: https://gist.github.com/q1p/0b95633ab9367fb86785
    Still not perfect solution, but technically more efficient then listed above and on StackOverflow. It's based on HashSet and default item id (getItemId()), so performance should not be an issue, as contains() method has O(1) time complexity and actually set will contain zero or one item most of the time.
    Also it's depends on Guava. If you are not using Guava, just replace set construction on line 91.
    To use it in your project you can just extend this class instead of CursorAdapter and add few lines of code in onDismiss():

        @Override
        public EnhancedListView.Undoable onDismiss(EnhancedListView enhancedListView, int i) {
            adapter.putPendingDismiss(id);
            adapter.notifyDataSetChanged();
            ...
        }
    

    This solution will not work if you're using list dividers (because this adapter displays empty view instead of dismissed item). You should add margins in your item layout to make spacing between items and include divider in item layout.

    Have a nice swiping.

  12. malinkang commented on Jul 12, 2014

    @malinkang

    i meet the same problem

  13. m2kk commented on Nov 18, 2014

    @m2kk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions