4

I am fetching podcast feed from the DB and displaying it in the RecyclerView using LoaderManager.LoaderCallbacks. In my content provider:

  1. I register an 'Observer' in the content resolver query() method with: cursor.setNotificationUri(getContext().getContentResolver(), uri);
  2. When I update/insert/delete in the content resolver, I notify Observer with: getContext().getContentResolver().notifyChange(uri, null); so onLoaderReset() is triggered when needed

Then I have a service with the DownloadManager, that update a progress of my ongoing downloads to the database every second. The loader is constantly loading the data from the DB and I can see the changing progress of every downloading episode in the UI.

I think, this is a wrong approach of notifying a change and is very very slow, but I can't think of any better solution right now. Could you suggest any effective solution with RecyclerView and download progress?

My custom activity with RecyclerView

public class MyActivity
    implements LoaderManager.LoaderCallbacks<Cursor> {

    private RecyclerView mRecyclerView;
    private MyCustomRecyclerViewAdapter mAdapter;

    ...

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(this,
            myDataUri, null, null, null, null
        );  
    }   

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
    }

    ...
}

My custom adapter

public class MyCustomRecyclerViewAdapter
    extends RecyclerView.Adapter<EpisodeAdapter.AudioAdapterViewHolder> {

    ...

    public void swapCursor(Cursor newCursor) {
        mCursor = newCursor;
        notifyDataSetChanged();
    }

    ... 
}
Niorko
  • 103
  • 2
  • 12

2 Answers2

3

ReclyclerView is much more efficient (specially the notifyDataSetChanged() method) when the adapter has stable ids.

If your data have ids (e.g. from the DB), you should really consider making your adapter use setHasStableIds(true) and properly override getItemId(int position).

I am not sure this will completely solve your problem but it is definitely a good practice.

bwt
  • 17,292
  • 1
  • 42
  • 60
  • I can't use `setHasStableIds(true)` due to restriction: _"Cannot change whether this adapter has stable IDs while the adapter has registered observers."_. IDs are not changing, but related data are, so not possible. But thanks for your advice. – Niorko Jan 27 '16 at 16:40
  • 1
    setHasStableIds(true) is typically called only in the constructor. Stable id means that the same item has the same id, it does not mean that the data (item content) don't change (this kind of optimisation can be handled using payloads in the recent version of the RecyclerView) – bwt Jan 27 '16 at 16:47
  • thanks, I just forgot to implement `getItemId(int position)`... now it's working, but still it's quiet sluggish (due to cursor switching). – Niorko Jan 27 '16 at 19:00
  • I guess refreshing every second is a bit too fast. Just out of curiosity, did the stable ids optimization help a bit, or not at all ? – bwt Jan 28 '16 at 11:01
0

Don't use notifyDataSetChanged() but rather notifyItemInserted() or notifyItemRangeInserted() for a performance gain.

for a similar issue that has been solved, look at this

Community
  • 1
  • 1
Shark
  • 6,513
  • 3
  • 28
  • 50
  • The ViewHolder is flickering when using **notifyItemChanged()** and still it's very inefficient and scrolling is not smooth. The idea is, that swapping the whole cursor every second or every half second is not good. I think of registering some broadcast receiver for downloading element would be more effective, but it will have to be registered in the **onBindViewHolder()**, but that's also not a good practice. – Niorko Jan 27 '16 at 13:13
  • Whatever works without crashing, looks acceptable and gets the job done *is* a good practice IMHO. – Shark Jan 27 '16 at 13:13
  • So when I register **BroadcastReceiver** for the ViewHolder element in the **onBindViewHolder** to receive progress data instead of fetching it from the DB, when could I unregister it? – Niorko Jan 27 '16 at 13:16
  • in `onPause()` and `onFinish()` of course. and re-register in `onResume()` – Shark Jan 27 '16 at 15:03
  • 1
    No, we are talking about RecyclerView adapter and about attaching receiver to the element in the list - in the `onBindViewHolder()` method, there is no `onPause()`, etc... – Niorko Jan 27 '16 at 15:07
  • You will either need callbacks or try living without unregistering receivers which, hopefully, won't cause (many) crashes. – Shark Jan 27 '16 at 15:30