Search code examples
androidandroid-loadermanagerandroid-loader

LoaderManager fires all Loaders same time


Firstly let me describe my case: I'm receiving list of categories which populate RecyclerView. Each View on this list is some kind of widget and most common is another list (horizontal RecyclerView) with some news (also parsed from JSON).

I'm keeping all stuff in ContentProvider and SQLiteDatabase (with helper), which contains two tables - for categories and for news (each have category id key). So I'm using Loader for each widget to load data from database or if missing I'm downloading proper data and inserting into db. And then loader automatically refresh list delivering new cursor.

At once there may be present on the screen two (or more) horizontal lists with news. Each of these lists is registering own Loader like here:

loaderManager.initLoader(getWidgetCategory().getCategoryID(), null, getLoader());

under getLoader(); we have same Loader (for widgets with news) like below:

private class NewsLoader implements LoaderManager.LoaderCallbacks<Cursor> {

    @Override
    public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
        return new CursorLoader(getContext(),
                CustomContract.News.CONTENT_URI,
                CustomContract.News.PROJECTION_ALL,
                CustomContract.News.KEY_CATEGORY_ID+"=?",
                new String[]{String.valueOf(getWidgetCategory().getCategoryID())},
                CustomContract.News.ORDER_BY_DEFAULT
        );
    }

    @Override
    public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
        if (data.getCount()>=15) {
            swapCursor(data);
            displayContent();
        }
    }

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

And the problem is: when I download and insert to database news from only one category all the Loaders are firing refreshing lists and it is really bad for performance... For example - there are two horizontal lists with news on the screen, we are scrolling a bit down and another (third) is coming. It doesn't have any news so background task is downloading them and inserting into db and then LoaderManager fires Loader for third widget. BUT not only... Also Loaders attached to two lists above are firing refreshing own lists.

I'm assuming there is a problem with same CONTENT_URI, maybe other params in CursorLoader constructor. The only change in there (for every news horizontal list widget) is WHERE clause with different category id and all Loaders are getting content from the same db table. How can I keep firing only one Loader for widget, which downloaded/inserted news? Note that id of registered loader and WHERE selection are the same...

Like I wrote there might be different types of widgets (and also lists, but not with news), which are populated from other tables and their Loaders are not firing with news Loaders. I'm assuming that if I get list of categories with only just-text-widgets (also shared db table) downloading text for one of the widgets will fire all `Loader's and redraw other widgets.

edit:

In other, short words: in my custom ContentProvider I have overriden query method, which contains line:

@Override
public Cursor query(
        final Uri uri,
        final String[] projection,
        final String selection,
        final String[] selectionArgs,
        String sortOrder
) {
    //selection, etc.
    //newly created cursor from selection above
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
}

and it's notifying all observing Loaders with passed uri. I have few lists (does not matter in fact that these are RecyclerViews) which are consuming data from one URI, but are passing custom selection param to each CursorLoader. But URI is common for all so all loaders are notified and I want to notify only this one, which fired database activity. I want to notify only one Loader with known id, which is also passed to query method inside selectionArgs. Yeah, I know that this might be impossible inside query, but maybe another way? For notifying Loaders not only by their URI


Solution

  • URI is common for all so all loaders are notified and I want to notify only this one, which fired database activity.

    The easiest fix is to create a URI pattern where the URI is different for each category, so when you call Cursor.setNoficationUri() each cursor (and ultimately, each loader) will only receive ContentChanged notifications for the data it's concerned with.

    There's no way to turn off the ContentObserver on a CursorLoader, so if you absolutely positively can't have different URIs, then the only other thing I can suggest is to write a Loader<Cursor> subclass that knows when to register and unregister its observer on the cursor.

    (Edited from earlier answer)