LoaderManager fires all Loaders same time - android

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

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)

Related

Update or Refresh UI with the update of database android

I have an image gallery. I have to show images with uploaded or not uploaded icon. A service is running in the background to upload the images and update the database.Suppose i am in gallery screen and some not uploaded images has been uploaded and update database. So i want to update my view without press any button.
whenever database is getting updated, is there any way i can get callback ??
what is the best approach to update my view without reload all??
You will need to use CursorLoaders/CursorAdapters. You can find an example here
https://github.com/ksarmalkar/ContentProviderJoin
Two possible ways to update the UI would be:
NotifyDatasetChanged() method which can be used if you are using an adapter to display the images (I would assume you are using an adapter for your gallery). You can call this method each time an upload completes which would then tell the view to update itself.
Otherwise maybe run an AsyncTask which checks the status of the upload every so often and in the onProgressUpdate() method have a callback to your UI thread to update that the image has been uploaded.
I hope this helps.
You can use loader for this.
Loaders have these characteristics:
They are available to every Activity and Fragment.
They provide asynchronous loading of data.
They monitor the source of their data and deliver new results when the content changes.
They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data.
Find here a get started help.
So :
First you should init you loader like this:
getLoaderManager().initLoader([YOUR_LOADER_ID], null, this);
Define loading instruction in onCreateLoader() :
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// You can return other type than cursor or create your own custom cursorloader but I guess that cursor is right fro you since you want load database content.
Uri baseUri = [you media uri , suppose that you sue content provider];
return new CursorLoader(getActivity(), baseUri,
[projection], [select], [arg],
[orderby]);
}
You get onLoadFinished() callback when data finishes to be loaded :
public void onLoadFinished(Loader loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
In your case, loader monitors every change in gallery database. So everytime there is a change, swap cursor is called automatically, then your view is updated according what you defined in your adapter.

How to Implement multiple loaders in ListFragment

I currently have a list fragment that loads a list of contacts from the ContactsContract.Data.CONTENT_URI using the LoaderManager.LoaderCallbacks. I also have a sqlite table that has contacts that have been added manually into my app by the user, I am using a ContentProvider for my other table.
I would like to load both the sqlite table as well as the ContactsContract.Data.CONTENT_URI using LoaderManager callbacks but I have not been able to find any examples as to how this can be done.
Is this even possible, can some one point me to an example of how to implement this behavior?
you supply a different id for each loader you want so that you can decipher between what loader is called
getLoaderManager().initLoader(0,null,this);
getLoaderManager().initLoader(1,null,this);
then in your onLoadFinished just get the if of the loader that was called
public void onLoadFinished(Loader<Cursor> loader, Cursor arg1) {
loader.getId()
....
}

Using CursorAdapter when displaying a single item

I have a ListFragment which displays a list of items obtained using a CursorLoader and a CursorAdapter as per the standard way of handling views of lists. This in itself is working fine,however I'm unsure as to how to use the same pattern to display the detailed view of a selected item.
A previous StackOverflow question discussed this but neither of the solutions suggested seem to me to be optimal. Using a ListView with a single item seems like overkill and wasteful of resources, whereas carrying out the work directly in the activity seems to be overloading the activity with data access and UI functionality. It also loses the ability to keep data in sync between the activity and the underlying storage.
Is there some middle ground that retains the split between loader, activity and adapter but for a single item rather than a list?
You can do without the Adapter and the ListView.
The CursorLoader returns you a Cursor. You can access the cursor without passing it to an adapter.
Just make sure the Cursor contains at least your one row.
if (cursor != null && cursor.moveToFirst()) {
// do whatever, ie String myString=cursor.getString(0)
// in case you fetch a string as the first element of your projection
}

Is it actually unsafe to call stopLoading on a Loader that is being managed?

The android documentation for Loader#stopLoading() says:
When using a Loader with LoaderManager, you must not call this method
yourself, or you will conflict with its management of the Loader.
But is that really true? Specifically I am interested in CursorLoader. I looked the through the Android source for version 4.2 and it seems pretty benign. Has anyone tried using this method and seen a problem? Is there an alternative if I want to keep the current active Cursor last delivered by the CursorLoader and also stop it temporarily from reloading due to the internal ContentObserver being triggered? Basically I want to make a bunch of changes to the ContentProvider that is the source of this managed Cursor and I don't want the Loader kicking off a ton of loads until I am done.
I agree with a.ch that relying on the Android source code isn't the way to go not just because you risk breaking your app with future updates but also because of the hundreds of modified versions of Android (the joy of fragmentation). stopLoading() wouldn't work anyway, for reasons see below.
cancelLoad() will not close the underlying cursor (where did you get that from?) so while this method will cancel a load if one is in progress, it won't prevent future loads. And even if it would prevent future loads it wouldn't work either (for the same reasons stopLoading() won't work).
My initial idea to solve this was to implement a custom CursorLoader and just override the onContentChanged() method using a flag to decide whether to "take" the changes or not. This way one could temporarily disable the update:
class MyCursorLoader extends CursorLoader {
private boolean mDontUpdate;
public MyCursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
}
#Override
public void onContentChanged () {
if (! mDontUpdate) {
super.onContentChanged();
}
}
#Override
public boolean takeContentChanged () {
return mDontUpdate ? false : super.takeContentChanged();
}
void dontUpdate(boolean dontUpdate) {
mDontUpdate = dontUpdate;
}
}
You would use this class instead of the CursorLoader in the onCreateLoader(int, Bundle) and you would set the don't update flag during the database update.
Now that works nicely except it doesn't. While it's not clear from the question what the CursorLoader and the resulting Cursor are used for I assume the Cursor is used in a CursorAdapter for a ListView or a similar widget.
The widget registers its own ContentObserver to the Cursor and updates the views accordingly. So even if no new Cursor is loaded a ListView (a GridView or any other widget attached to the Cursor) would still update (Android would call the newView, bindView methods in a CursorAdapter).
This brings me to my next approach.
Although it's not clear from your question I assume you are doing some modifications to a database? If that's the case then why not do all the modifications in a single transaction? If done in one transaction changes wouldn't show anywhere before the transaction is committed and that's probably what you want.
If your Cursor isn't backed by a database though you would probably have to remove the Adapter from your widget during the update.
First of all, you shouldn't rely on the android source code: if there are no any issues with the current android build, it doesn't mean there won't be any in future. I'd suggest considering android system as a black box, this will let you write robust and reliable apps.
According to your question on the alternative, consider cancelLoad().

Android Cursor Management w/ListView

I've got a ListActivity that displays a list of items from a database using a CursorAdapter, which initially contains all items in the table. I also provide an EditText view where the user can enter search text, and as characters are entered, I requery the database using a "LIKE" or "MATCH" where clause to filter the results (IOW, what lots of apps do when searching).
Currently, I do this in an AsyncTask by creating a new Cursor from the query, creating a new instance of my CursorAdapter class, and then calling list.setAdapter from the UI thread when the task completes. This is all working, but is there a more elegant way of effectively requerying the database with a new WHERE clause from withing the existing adapter/cursor and avoiding having to create new object instances each time? Any examples of this technique?
Doug Gordon
GHCS Software
First of all, CursorAdapter has an changeCursor-Method, were you can change the Cursor without changing the adapter itself. When changing the Cursor the corresponding AdapterView will automatically be notified and update itself.
For further abstraction, you might provide a business object that wraps the query and optionally the execution of the query and provide it to both the Adapter and the TextView (or the Activity possessing the TextView). The TextView changes the business object, the object creates a new cursor and tells the Adapter (using the Observer Pattern, e.g. a Listener) that the cursor changed, and the Adapter retrieves the new Cursor and updates itself.

Categories

Resources