Update or Refresh UI with the update of database android - 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.

Related

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
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)

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().

Merging cursors during onLoadFinished() causes StaleDataException after rotation

I'm loading some results from a database using a loaderManager. Unfortunately, the following code produces a StaleDataException after rotating the device:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
// If we've returned results, then pass them and the webSearches cursor along to be displayed
if(cursor.moveToFirst())
{
// Get a cursor containing additional web searches and merge it at the end of the results cursor
MatrixCursor searchesCursor = getWebSearchesCursor(searchTerm, false);
Cursor[] cursors = { cursor, searchesCursor };
// TODO: Figure out why merging cursors here causes staledataexception after rotation
Cursor results = new MergeCursor(cursors);
// Display the cursor in the ListView
adapter.changeCursor(results);
}
// If no results were returned, then return suggestions for web searches
else
{
// Get a cursor containing additional web searches
MatrixCursor noResults = getWebSearchesCursor(searchTerm, true);
adapter.changeCursor(noResults);
}
// Show the listView and hide the progress spinner
toggleListView(SHOW);
}
The call to getWebSearchesCursor() returns a MatrixCursor with some additional search prompts to accompany any returned results. I discovered that changing adapter.changeCursor(results) to adapter.changeCursor(cursor) fixes the error, so it looks like merging a MatrixCursor to the returned cursor produces the error.
My question is, why?
If any results are returned, I'd like to be able to add additional items to the returned cursor so the user has the option to perform their search on a couple of websites. Is there a better way to merge cursors so that I don't get this exception after rotation?
If you've started using swapCursor() instead of changeCursor() everywhere, then I hope you've also started handling cursor closing in those places.
changeCursor() will close the old cursor, this is intentional and works flawlessly when you're just directly using the cursor provided by onLoadFinished(). It is done this way so you don't have to worry about closing it.
When you rotate your device, the android system will check that the cursor it sent you last time has not yet been closed, and sends it again rather than spending resources on creating a new one. Your code wraps this cursor in a new instance of a MergeCursor which gets passed to changeCursor(), which sees that this is not the same object it got before, and decides to close the old instance. Since MergeCursor only wraps the cursors you pass it, rather than copying the data in them, your new instance now contains (at least) one closed cursor.
To correctly handle this you'll need to write some code of your own that checks if the cursor you get through onLoadFinished() is the same as one you have in your current MergeCursor instance, and only close the existing instance if you are getting a new cursor. Of course you'll also need to keep track of the other cursors wrapped in the same MergeCursor instance.
This issue came up again a couple of days ago and I was fortunate enough to stumble upon a solution.
I found out that I should have been using swapCursor() instead of changeCursor(). According to the Android docs:
Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor), the returned old Cursor is not closed.
...
If the given new Cursor is the same instance is the previously set Cursor, null is also returned.
That last part seemed to be the key. The error mentioned in the question above could be traced back to the CursorAdapter choking on the merged cursor because it was closed when it tried to redraw the fragment after a rotation. By using swapCursor() instead, the CursorAdapter was able to reuse the "old" merged cursor instead of questioning its validity and throwing a StaleDataException.
I'm making some suppositions here; perhaps someone more knowledgeable in the inner-workings of Android could confirm or deny my reasoning.

SpinnerButtonAdapter using ASyncTask to auto select from database

I have a spinner which is populated from the database. This in turn is pulled from a webservice so it is not possible to hard-code the values.
The spinner is part of a form that the user fills out and saves and I need them to be able to go back in and edit the values, so I am trying to auto-populate it based on what was selected.
The problem is that the spinner does not always select, which happens more often than not. I think this is due to the fact that I am loading the data asynchronously using an asynctask, and by the time it retrieves the data the spinner has yet not been loaded. I have logging showing that, for example, spinner item 4 was pulled from the database, but the spinner shows nothing selected.
What is the best way to find out when the loader has been completed so that I can populate (ie select) the relevant item in the list?
I put the call at the end of the load finished method,
#Override
public void onLoadFinished(Loader<Cursor> aLoader, Cursor aCursor) {
int id = aLoader.getId();
Loader<Cursor> cursorLoader = null;
switch (id) {
mAdapter.swapCursor(aCursor);
}
if (inEditMode) {
fillInValues();
}
}

How to maintain ListView position when using the new Loader APIs?

In Honeycomb the Loader APIs were introduced as the proper way to provide data to an application by doing the heavy lifting on a background thread. In my application I'm working to replace all my Cursors with Loaders that return Cursors. Since Cursor.requery() is depreciated now, it is recommended to just call restartLoader and allow the work to again be done on a background thread and then changeCursor when it returns in onLoadFinished.
All of this works wonderfully except that the ListView doesn't maintain it's scroll position when I want to requery the data, using Cursor.requery() this used to work because it was the same Cursor instance with updated data.
How can I refresh my Loader without loosing the scroll position?
The Loader class by itself doesn't know when the dataset changes and is implementation specific to CursorLoader currently. Now inside my application the data I needed to refresh is inside a local SQLite database (no ContentProvider) so I had to modify the CursorLoader to use my local database instead, as Dianne Hackborne mentioned on the developer group.
Unfortunately just returning a Cursor from loadInBackground doesn't allow the ContentObserver to properly notify the Loader when the data changes, this is because AbstractCursor only calls notifyChanged() when requery() is called on the Cursor.
Since the ContentObserver will never notify the Loader that the data has changed, I ended up calling Loader.onContentChanged() myself as needed. At this point it everything seems to be working but I'll update this answer if any significant issues arrise.
You must just do not create a new adapter in each Loaders loading; I mean..
#Override
public void onLoadFinished(Loader<List<NotificationItem>> loader, List<NotificationItem> notifications) {
// We must do it in this way to not losing listview scroll position after a reload.
if(mAdapter==null) {
mAdapter = new HomeUserActivityListAdapter(getActivity(), notifications);
mListView.setAdapter(mAdapter);
}else{
mAdapter.refill(notifications);
}
}
And in you adapter create a method to items refill
public void refill(List<NotificationItem> notifications) {
mNotificationsList.clear();
mNotificationsList.addAll(notifications);
notifyDataSetChanged();
}
And that's all, it will maintain your scroll position exactly :-)

Categories

Resources