in my application I am displaying a list of items queried from the database with the use of Loader Manager and Content Provider. The results are attached to a SimpleCursorAdapter and then displayed on the List View. I would like to be able to load, let's say up to 20 items at first and when scrolling to the end of that list, load up additional 20 items. Up to now, I used similar approach as here:
How to update listview whose data was queried from database through SimpleCursorAdapter?
i.e. limiting query results to 20 and increasing the number appropriately in the OnScrollListener and restarting the loader with new instructions.
The problem is with this statement:
incomeAdapter.swapCursor(data);
It completely resets adapter's data, therefore only the second batch of items are displayed on the list. Is there any way to simply load additional items on top of existing ones? Thanks
EDIT:
Merging cursors like this:
if (incomeAdapter.getCursor() != null){
Log.v("Income Activity", "current adapter");
Cursor oldData = incomeAdapter.getCursor();
MergeCursor merge = new MergeCursor(new Cursor[] {oldData, data});
incomeAdapter.swapCursor(merge);
}
else {
Log.v("Income Activity", "no current adapter");
incomeAdapter.swapCursor(data);
}
throws an exception:
07-13 16:56:15.455: E/AndroidRuntime(5665): FATAL EXCEPTION: main
07-13 16:56:15.455: E/AndroidRuntime(5665): android.database.StaleDataException:
Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated
prior to calling this method.
EDIT2:
As per request, the code where data is requested:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
orderBy = sortOrder + " LIMIT " + limitSkip + "," + limitCount;
CursorLoader cursorLoader = new CursorLoader(this,
DatabaseProvider.CONTENT_URI1, PROJECTION, null, null, orderBy);
return cursorLoader;
}
You need to use the previous cursor: incomeAdapter.getCursor() and merge it with the new data.
MergeCursor comes to your aid there:
Cursor oldData = incomeAdapter.getCursor();
MergeCursor merge = new MergeCursor(new Cursor[] {oldData, data});
incomeAdapter.swapCursor(merge);
EDIT:
You are using a CursorLoader. You don't need to re-query the database or LIMIT your queries. The loader does all that for you when specific data is requested.
Android Docs: Loaders
Related
I have a little problem with SQLite and ContentProviders. First of all here is my current code:
public void loadFavoriteMovies(){
movieList.clear();
Cursor cursor = getContentResolver().query(FavoriteMoviesContract.MovieEntry.CONTENT_URI,
null, null, null, FavoriteMoviesContract.MovieEntry._ID);
if(cursor != null){
while(cursor.moveToNext()) {
MovieResults.Movie movie = new MovieResults.Movie(
cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_TITLE)),
cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_IMAGE)),
cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_BACKDROP)),
cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_RELEASE)),
Double.valueOf(cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_RATING))),
cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_PLOT)),
Integer.valueOf(cursor.getString(cursor.getColumnIndex(FavoriteMoviesContract.MovieEntry.COLUMN_ID)))
);
movieList.add(movie);
}
cursor.close();
}
Log.v("Size of Favorites:", String.valueOf(movieList.size()));
mAdapter.setmMovieList(movieList);
}
What I'm trying to do is I'm reading the SQLite database and putting the values there into a Movie object. But my problem is I want to do this operation using CursorLoader. How can I achieve this?
My main aim for doing this is because Cursor does not automatically update the value when I delete an item from the database and I need to reload the activity which the items are displayed. If anyone can offer an alternative way, that'd also be appreciated.
Bounty Award - The bounty will be awarded to an answer that gets from a populated Telephony.Sms.Inbox.PERSON value, to the associated Contact using only ContractsContact tables.
I'm reading SMS messages in the standard way in my application:
final String[] projection = {Telephony.Sms.Inbox.BODY,
Telephony.Sms.Inbox.ADDRESS,
Telephony.Sms.Inbox.READ,
Telephony.Sms.Inbox.DATE,
Telephony.Sms.Inbox.PERSON};
final Cursor cursor = ctx.getContentResolver().query(Telephony.Sms.Inbox.CONTENT_URI,
projection, null, null, Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);
When populated, the id returned from the index Telephony.Sms.Inbox.PERSON relates to the id of the deprecated Contacts.People._ID and can be used to query further contact information in the following way:
final String[] projection = {Contacts.People.DISPLAY_NAME};
final String[] selectionArgs = {contactId};
final Cursor cursor = ctx.getContentResolver().query(Contacts.People.CONTENT_URI,
projection, Contacts.People._ID + " = ?", selectionArgs, null);
Why would the relatively new Telephony API use deprecated tables, instead of ContactsContract?
Telephony.Sms.Inbox.PERSON documentation states:
Type: INTEGER (reference to item in content://contacts/people)
I've tried unsuccessfully (but not unsurprisingly?) to find a mapping to the id in any of the ContactsContract id fields, so I'm left having to use deprecated APIs in order to resolve the queries I need to perform quickly.
Such queries include searching for messages by a particular contact, for which I only have the name. The contact could have multiple numbers, which may not be in the correct format to potentially match Telephony.Sms.Inbox.ADDRESS entries.....
The workaround of using Telephony.Sms.Inbox.ADDRESS and ContactsContract.PhoneLookup is not the end of the world when going from the number to the contact, but I still feel I must be missing something here?
Here is the process I'm using to get the messages for 'Joe Bloggs'.
1) Query the ContactsContract table to confirm a contact by the name of Joe Bloggs exists on the device - or get a close match if the contact is actually listed as 'Joe Blogs'.
2) Using the confirmed name, I query the deprecated Contact.People table to get all associated ids for the contact in the following way:
final String selection = Contacts.People.DISPLAY_NAME + " LIKE ?";
final String[] projection = {Contacts.People.DISPLAY_NAME,
Contacts.People._ID};
final String[] selectionArgs = {contactName};
final Cursor cursor = ctx.getContentResolver().query(Contacts.People.CONTENT_URI,
projection, selection, selectionArgs, null);
3) Using the list of deprecated contact ids, I query the message table as so:
final String[] referredArgs = new String[contactIdArray.size()];
for (int i = 0; i < contactIdArray.size(); i++) {
referredArgs[i] = contactIdArray.get(i);
}
final String referredSelection = Telephony.Sms.Inbox.PERSON + " IN "
+ "(" + TextUtils.join(",", referredArgs) + ")";
final String[] projection = {Telephony.Sms.Inbox.BODY,
Telephony.Sms.Inbox.ADDRESS,
Telephony.Sms.Inbox.READ,
Telephony.Sms.Inbox.DATE,
Telephony.Sms.Inbox.PERSON};
final Cursor cursor = ctx.getContentResolver().query(Telephony.Sms.Inbox.CONTENT_URI,
projection, referredSelection, null, Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);
I'm hoping someone will tell me I'm going round the houses here and there is a more obvious solution using current APIs. I don't consider iterating the entire message table using ContactsContract.PhoneLookup an optimised solution.
Thanks in advance.
I wouldn't use the Telephony.Sms.Inbox.PERSON field, and definitely wouldn't query the deprecated People apis if I were you.
The People apis had been deprecated for so long you can't count on all devices our there to properly support it anymore.
First thing you need to understand is that there isn't a one-to-one link between sms and contacts.
An SMS can come from a non-contact phone number, a single contact, multiple contacts, a mixture of contacts and non-contacts, alpha-numeric ids, and even other, more rare options.
Next thing, you should read carefully the stock code and how it handles a properly called "Recipient ID" that you can get from the SMS collection, there's a collection called canonical-addresses (or canonical-address) that serves as a mapping between a phone number (or a comma-separated list of phones) and a recipient id.
The code does a single query on launch to cache the entire table in memory, and then uses it to map between phones and recipient-ids.
Here's the mapping class
Why would the relatively new Telephony API use deprecated tables, instead of ContactsContract?
What you are referring to is not new. In Telephony.java, you see it relies on the existing content://sms provider:
public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
/**
* The {#code content://} style URL for this table.
*/
public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
It was already there in Donut (and probably before, but I didn't check).
What's new in Kitkat is the ability to change the SMS app.
It's been five years and it's still relevant. You still need to do endlessly phoneLookup and hang up callbacks on contact tables if all you need to do is synchronize text messages.
I do not understand your concern properly but I am working on similar project, here is the basic code, and basic, important columns for fetching and display a message:
ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"*"};
Cursor SMSL = contentResolver.query(Telephony.Sms.CONTENT_URI, projection, null, null, "date ASC");
int msgscount = SMSL.getCount();
if (msgscount>0) {
msgs = new String[SMSL.getCount()][msgs_column_count];
int i = 0;
while (SMSL.moveToNext()) {
progress.setProgress(i);
msgs[i][0] = SMSL.getString(SMSL.getColumnIndex("address"));
msgs[i][1] = SMSL.getString(SMSL.getColumnIndex("date_sent"));
msgs[i][2] = SMSL.getString(SMSL.getColumnIndex("date"));
msgs[i][3] = SMSL.getString(SMSL.getColumnIndex("type"));
msgs[i][4] = SMSL.getString(SMSL.getColumnIndex("body"));
msgs[i][5] = SMSL.getString(SMSL.getColumnIndex("read"));
if (SMSL.getString(SMSL.getColumnIndex("service_center")) != null){
msgs[i][6] = SMSL.getString(SMSL.getColumnIndex("service_center"));
}else{
msgs[i][6] = "";
}
i++;
}
SMSL.close();
}else{
msgs = new String[0][0];
Toast.makeText(getApplicationContext(),"No messages found!",Toast.LENGTH_LONG).show();
}
If you want any help with this or fetching messages, let me know.
I'm seeing a crash report for this occassionally:
Fatal Exception: java.lang.IllegalStateException: Couldn't read row 1127, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetLong(CursorWindow.java)
at android.database.CursorWindow.getLong(CursorWindow.java:511)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:220)
at android.database.AbstractCursor.moveToNext(AbstractCursor.java:245)
at android.database.CursorWrapper.moveToNext(CursorWrapper.java:166)
at com.anthonymandra.util.ImageUtils.cleanDatabase(SourceFile:381)
Apparently the moveToNext is failing mid-loop (note row 1127). The loop removes entries that represent files that can no longer be found.
final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
try( Cursor cursor = c.getContentResolver().query(Meta.CONTENT_URI, null, null, null, null))
{
if (cursor == null)
return;
final int uriColumn = cursor.getColumnIndex(Meta.URI);
final int idColumn = cursor.getColumnIndex(BaseColumns._ID);
while (cursor.moveToNext())
{
String uriString = cursor.getString(uriColumn);
if (uriString == null) // we've got some bogus data, just remove
{
operations.add(ContentProviderOperation.newDelete(
Uri.withAppendedPath(Meta.CONTENT_URI, cursor.getString(idColumn))).build());
continue;
}
Uri uri = Uri.parse(uriString);
UsefulDocumentFile file = UsefulDocumentFile.fromUri(c, uri);
if (!file.exists())
{
operations.add(ContentProviderOperation.newDelete(Meta.CONTENT_URI)
.withSelection(getWhere(), new String[]{uriString}).build());
}
}
}
c.getContentResolver().applyBatch(Meta.AUTHORITY, operations);
Any idea how a cursor could fail mid-loop like that?
You appear to be making a fairly large query: at least 1127 rows, and for all possible columns (despite the fact that you are only using two of them). And, during your work with that Cursor, you are doing disk I/O and/or IPC back to the ContentProvider, assuming that UsefulDocumentFile is related to Android's DocumentFile.
As Prakash notes, the Cursor that you get back may contain only a subset of the information. As soon as you try advancing past that point, the Cursor needs to go back to the data source and get the next window of results. I can see you running into this sort of problem if there has been a substantial change in the data while this work has been going on (e.g., there are now fewer than 1127 rows).
I suggest that you:
Constrain the columns that you get back to the subset that you need, and
Avoid the I/O during the loop (e.g., spin through the Cursor to build up an ArrayList<Pair> or something, close the Cursor, then iterate over the list)
may be you are srote file in database that's why you get java.lang.IllegalStateException
UsefulDocumentFile file = UsefulDocumentFile.fromUri(c, uri);
Android SQLite returns rows in cursor windows that have the maximum size of 2MB as specified by
config_cursorWindowSize.If your row exceeds this limit, you'll get this error.
Store files in filesystem and paths in database.
The following code throws the exception in the title on the third and fourth line. Am I missing something? I want to be able to sort by artist name.
public List<String> getAllArtists(Context context) {
List<String> artists = new ArrayList<String>();
String[] projection2 = {MediaStore.Audio.Media.ARTIST};
String sortOrder = MediaStore.Audio.Artists.ARTIST;
Uri songUri = Uri.parse("content://com.google.android.music.MusicContent/audio");
CursorLoader cl2 = new CursorLoader(context,
songUri, projection2, null, null, sortOrder);
cursor = cl2.loadInBackground();
while (cursor.moveToNext()) {
if (cursor.getString(0).length()>0){
if (!artists.contains(cursor.getString(0)))
artists.add(cursor.getString(0));
}}
cursor.close();
return artists;}
Here is the complete stack trace:
03-22 16:49:18.237 2594-2637/php_request E/AndroidRuntime: FATAL
EXCEPTION: IntentService[SongService] Process: php_request, PID: 2594
java.lang.IllegalArgumentException: sortOrder not supported
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:421)
at android.content.ContentResolver.query(ContentResolver.java:500)
at android.content.CursorLoader.loadInBackground(CursorLoader.java:64)
at SongParser.getAllArtists(SongParser.java:41)
at SongService.onHandleIntent(SongService.java:60)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.os.HandlerThread.run(HandlerThread.java:61)
What you're using is a Uri handled specifically by Google Play Music app, not Android media system. (com.google.android.music is package name of Play Music.) I can imagine that
a) Play Music API cannot be mixed with Android MediaStore API,
b) Play Music content provider does not support sorting in SQL as the error suggests,
c) Play Music API is changing over time so don't use it anyway. (Don't quote me on this.)
Maybe you'd like to use one of Uri constants specified here http://developer.android.com/reference/android/provider/MediaStore.Audio.Media.html. These are:
MediaStore.Audio.Media.INTERNAL_CONTENT_URI
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
If using specified Uri is intended then I suggest removing sortOrder from content provider request and sorting the resulting List afterwards by calling
Collections.sort(artists);
as described here https://stackoverflow.com/a/708708/2444099.
Seems like perhaps sort order API has changed. Can you sort on ARTIST_KEY? I'm just looking at columns on http://developer.android.com/reference/android/provider/MediaStore.Audio.AudioColumns.html and that one says it's sortable. Also does your query work when you have null sort order?
For your case, you can use it for sorting,
String sortOrder = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;
For URI, you can use-
Uri uri = MediaStore.Audio.Artists.Albums.getContentUri("external", artistId);
The artistId is the value of the MediaStore.Audio.Artists._ID from a previous Artist query.
Here,
DEFAULT_SORT_ORDER: The default sort order for this table
Constant Value: "artist_key"
Hope it will help you.
What CursorLoader does?
CursorLoader do sort for you when it is created as CursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
So, to query for a list of artist, you would use MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI and use the artist name as your selection args.
For a list of albums, you now have the ARTIST_KEY which you can use to query MediaStore.Audio.Artists.Albums.EXTERNAL_CONTENT_URI, to obtain a list of albums for the given artist.
Resource Link: Media store default sort order
Sort order in Android list from Mediastore
For Video, you can use it
String orderBy = android.provider.MediaStore.Video.Media.DATE_TAKEN;
videocursor = managedQuery(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
proj, null, null, orderBy + " DESC");
Resource Link: sort order in android list from mediastore
Query Sorting using column name.
For specifying the sort order for the query to a content provider, it look like
new CursorLoader(context, RepresentativeEntityContentProvider.CONTENT_URI, null, null,
null, "COLUMN_NAME ASC");
Using this we can make it ascending order with COLUMN_NAME.
Android SQLite Sort order does not work with upper and lowercase letters
If you are using SQLite database, with a Cursor or Perhaps Content provider, you may have come across alphabetical sorting problems regarding upper and lowercase letters.
String[] projection = { Table.COLUMN_ID, Table.COLUMN_TITLE};
String sortOrder = Table.COLUMN_TITLE + " COLLATE NOCASE ASC";
CursorLoader cursorLoader = new CursorLoader(this,
YourProvider.CONTENT_URI,
projection,
null,
null,
sortOrder);
The solution to the alphabetical sorting problem is the COLLATE NOCASE in the part of the query where you specify the sorting.
Note that the ASC is optional to specify. You could also use DESC to reverse the sorting order.
I'm getting following exception reported by few users of my application.However I'm not getting the same when trying on my local device.
Would need advice as to how to approach towards solving the problem.
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetLong(Native Method)
at android.database.CursorWindow.getLong(CursorWindow.java:507)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:220)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:237)
Following are the details for the context:
The exception is coming on the table storing the images as blob. The table definition is
photo_id String,
blob_image Blob
blob_date String
The code to get the blob data is as following
byte[] photo= null;
Cursor cursor = null;
String criteria = " photo_id = ?";
String[] arguments = {photoid};
try {
cursor = database.query("MY_GALLERY", null,criteria, arguments, null, null, null);
if(null !=cursor){
int rowCount = cursor.getCount();
if(rowCount>0){
cursor.moveToFirst();
int colIndex = cursor.getColumnIndex("blob_image");
if (colIndex != -1) {
photo = cursor.getBlob(colIndex));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
As I'm trying to fetch blob value only if the row count is greater than 0 after checking cursor.getCount() I find it difficult
why cusrsor initialization is being raised as issue.
I also tried to check the size of image being saved. They are less than 400kb in size.
This error is also reported from users using Nexus 7 with Android version 4.4.4 and Samsung tablet SM-T116NY with android version 4.4.
The application is in use for over two years and I'm getting this issue for the first time .
I've checked similar and related issues already and most of the suggestions mentioned [with respect appropriate data base initialisation,
handling of cursor index, opening/closing of cursor, row size limit less than 2mb are taken care of]
One of the reason to store this in DB is to have an alternate storage where user do not delete stuff by mistake.
In application the image from DB is used only if the image is not found in file system.
Any recommendation or suggestion?
thanks
Pradeep