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.
Related
I'm trying to mark all text messages as read when user opens my inbox. I've pieced together code from a few tutorials online and ended up with this:
Uri uri = Uri.parse("content://sms/inbox");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
while (cursor.moveToNext()) {
if ((cursor.getInt(cursor.getColumnIndex("read")) == 0)) {
String SmsMessageId = cursor.getString(cursor.getColumnIndex("_id"));
ContentValues values = new ContentValues();
values.put("read", true);
getContentResolver().update(Uri.parse("content://sms/inbox"), values, "read=0", null);
}
I just want to mark all text messages as read in the onResume() function on this activity. My code may be a pile of crap, like i said it's mashed together from a few places. Corrections to, or alternatives to, my code would be very appreciated. Compiling the code with the sdk for 5.1, testing on 4.4, my app is the default SMS app.
If you want to mark all messages as read, you can do that in one go.
ContentValues values = new ContentValues();
values.put(Telephony.Sms.READ, 1);
getContentResolver().update(Telephony.Sms.Inbox.CONTENT_URI,
values, Telephony.Sms.READ + "=0", null);
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
I'm working in an application that read phone contacts and use them in my application (Call history, Favorite contacts and All contacts).
My UI consist of tab host control and user can swap between them, as I want my data to be shared across all my activities and also to be saved in only one place.
So I have created a singleton class called data controller, and when I open the application I show loading screen until all data loaded.
The problem now that user is complaining because of waiting a lot of time about (1 minute) every time they open the application when he has a very large amount of contacts, so how can I optimize my code in a good way?
EDIT
This is the method that I'm using to get all contacts:
public static ArrayList<ContactInfo> getAllContactWithNumberAndNameAndPhoto(
Context context, boolean starred) {
ArrayList<ContactInfo> retList = new ArrayList<ContactInfo>();
ContentResolver cr = context.getContentResolver();
Cursor cur = null;
if (starred == true) {
cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null,
"starred=?", new String[] { "1" }, null);
} else {
cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null,
null, null);
}
if (cur.getCount() > 0) {
while (cur.moveToNext()) {
ContactInfo item = new ContactInfo();
String id = cur.getString(cur
.getColumnIndex(ContactsContract.Contacts._ID));
String name = cur
.getString(cur
.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
Uri photo = PhoneUtils.getPhotoUriFromID(context, id);
String starredValue = cur.getString(cur
.getColumnIndex(ContactsContract.Contacts.STARRED));
boolean isFav = false;
if (starredValue.equals("1"))
isFav = true;
if (Integer
.parseInt(cur.getString(cur
.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID
+ " = ?", new String[] { id }, null);
while (pCur.moveToNext()) {
String phoneNo = pCur
.getString(pCur
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
item.addPhone(removeCharactersFromPhoneNumber(phoneNo));
}
pCur.close();
if (photo != null) {
item.setPhoto(photo.toString());
}
item.setName(name);
item.setFavorite(isFav);
item.setRecent(false);
retList.add(item);
}
}
cur.close();
}
return retList;
}
Please let me know if I can optimize this method.
Im surprised it takes that long to load the contacts from the device.
Have you profiled the app to see where the time is actually spent? Something seems wrong here.
If it truly takes that long to load from the system providers (due to the OS) you could cache the results (i.e. put in your own SQL db) so you can load quickly on each app visit (< 1 sec) and refresh from device in the background.
I guess that the bottleneck of your method is photo loading. Try to load everything except photos, and then show your activity, concurrently loading photos.
Also you can try to create your own app table that contains just the data you need. So you'll do less selects while loading contacts. But you would have to synchronize your table. You can do so concurrently.
I have this simple application that I'm currently writing as practice. Its purpose is to allow the user to send a quote and the author of that quote on a server (in this case a Parse.com backend I have registered) and then show those quotes to other users of the app randomly. So by opening the app, you get a random comment that someone has posted.
The way I'm trying to accomplish this is:
On start-up, the app connects to the Parse.com backend and downloads all the currently available quotes (I call those Inanity objects because the quotes are supposedly enlightened but should actually be stupid and nonsensical - anyway, doesn't matter). This is the code:
query.findInBackground(new FindCallback<ParseObject>() {
SQLi sqlite = new SQLi(MainActivity.this);
SQLiteDatabase dbz = sqlite.getWritableDatabase();
#Override
public void done(List<ParseObject> list, ParseException e) {
//sqlite.dbDelete();
if (e == null) {
int size = list.size();
for (int i = 0; i < size; i++) {
ParseObject object = list.get(i);
String author = object.get("author").toString();
String content = object.get("content").toString();
Inanity inan = new Inanity(content, author, 1);
Log.d("FOR LOOP" + i, inan.toString());
sqlite.insertInanity(dbz, inan);
}
}
}
});
Pretty simple. (dbz is an SQLiteDatabase acquired by calling getWritableDatabase(), by the way). The code below is the code for the SQLiteOpenHelper insertInanity() method that I use to put the retrieved data from the server in the local SQLite database:
public void insertInanity(SQLiteDatabase db, Inanity inanity) {
ContentValues values = new ContentValues();
values.put(CONTENT_INANITIES, inanity.getContent());
values.put(AUTHOR_INANITIES, inanity.getAuthor());
values.put(UPVOTE_INANITIES, inanity.getUpvotes());
db.insert(TABLE_INANITIES, null, values);
}
I pass an SQLiteDatabase object to the method simply to avoid having to call getWriteableDatabase() - I had some trouble with recurring calls if I kept doing that.
After writing the server data on the local SQLite database, the user is taken to an Activity that starts showing the quotes and the author of the quotes in a couple of TextViews. This is the code the retrieves a quote/author object from the SQLite database:
public Inanity retrieveInanity(int id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_INANITIES, new String[] {
CONTENT_INANITIES, AUTHOR_INANITIES, UPVOTE_INANITIES },
ID_INANITIES + " = " + id, null, null, null, null);
if (cursor == null || cursor.getCount() == 0) {
return new Inanity("a", "b", 1);
}
else {
cursor.moveToFirst();
String contentL = cursor.getString(cursor
.getColumnIndex(CONTENT_INANITIES));
String authorL = cursor.getString(cursor
.getColumnIndex(AUTHOR_INANITIES));
int upvotesL = cursor.getInt(cursor
.getColumnIndex(UPVOTE_INANITIES));
Inanity inanity = new Inanity(contentL, authorL, upvotesL);
return inanity;
}
}
Finally, the quote to be displayed is randomly selected from the locally stored results thusly ("a" is an int variable declared earlier by the way)
final SQLi sql = new SQLi(this);
a = sql.getRowCount() + 1;
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Random rand = new Random();
int e = rand.nextInt(a);
if (e != 0) {
Inanity inanity = sql.retrieveInanity(e);
String content = inanity.getContent();
String author = inanity.getAuthor();
ImageView imageView = (ImageView) findViewById(R.id.downloaded);
TextView contentView = (TextView) findViewById(R.id.content);
TextView authorView = (TextView) findViewById(R.id.author);
Picasso.with(ShowActivity.this)
.load("http://img1.etsystatic.com/000/0/5356113/il_fullxfull.314192275.jpg")
.into(imageView);
contentView.setAlpha(0.9f);
authorView.setAlpha(0.9f);
Animation alpha = new AlphaAnimation(0.1f, 1.0f);
alpha.setDuration(2000);
contentView.setText(content);
authorView.setText(author);
contentView.startAnimation(alpha);
authorView.startAnimation(alpha);
}
else {
Toast.makeText(ShowActivity.this,
"Cursor trouble in wonderland!", Toast.LENGTH_LONG)
.show();
}
}
});
}
The getRowCount() method of the SQLi class is this:
public int getRowCount() {
int count = 1;
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_INANITIES, null);
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
count = cursor.getCount();
}
return count;
}
For the most part. this works great. So, what's the problem, I hear you ask? Well, since I want to refresh the quotes every time the application starts up and get fresh ones from the server, the way I'm trying to accomplish that is by deleting the contents of the Inanity table of the database and re-populate them on start-up. So, I have created this method in the SQLi database helper class that's called dbDelete() which I call right at the start of the done() method of the FindCallback class of the Parse.com library (although I have commented this out from this code, it works swimmingly: it deletes the contents of the database just fine). Unfortunately, when I do that, it appears that the local SQLite database is not repopulated on app startup for some infernal reason, so I keep getting the placeholder "a", "b" and 1 values that are returned when the retrieveInanity() method cannot find cursor contents. Here is the dbDelete() method, which is quite simple:
public void dbDelete() {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_INANITIES, null, null);
}
I have been trying to solve this for quite some time and it's driving me crazy. I understand that the question is pretty convoluted, big and that it doesn't contain any catchy NullPointerExceptions/logcat action but any help would be appreciated. I must be missing something obvious related to the SQLite database use but I simply can't figure it out.
I wrote a similar app (one that made calls to a remote database and updated the info on local db). You should try using db.insertOrThrow. You will need to wrap the method in a Try...Catch statement. It will try to insert rows, and will throw an exception when a row already exists. You can then ignore the errors by leaving the Catch part blank. This will avoid the deletion and rebuild of the table.
try {
db.insertOrThrow(TABLE_INANITIES, null, values);
} catch SQLException s {
\\do nothing, as we don't care about existing rows
}
If you set up the quote server to have unique identifiers for the quote, then the local copy, your SQLite DB, will not insert duplicate entries. For example, your quote DB table on the server would look something like this
ID | Quote | Author
1 | blah | J. smith
Where the column ID is set as the unique identifier (or unique key). When your app calls the server and queries the remote DB, your local DB has only records that don't exist added to it.
You also want to make sure, I believe, that you update your cursor adapter in onResume().
this doesn't always happen so i can't understand properly what's going on: my application take and modify a picture, then save it in external storage. If i try to open in the application a new saved pictures FROM A FILE MANAGER AND NOT FROM GALLERY, it crashes when executing cursor.getCount(), in DDMS i read the error:"cursor not closed before finally" this is the piece of code where the problem is, i can post more if necessary, thank you!
ps this code is taken from other answers here in stackoverflow, as you could expect i'm not an expert so please be patient with me, thanks
pps i can't see immediatly images in gallery after saving it, when they appear in gallery this error desappear.
public static int getOrientation(Context context, Uri photoUri) {
/* it's on the external media. */
Cursor cursor = context.getContentResolver().query(photoUri,
new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
if (cursor.getCount() != 1) { //HERE IS THE PROBLEM
return -1;
}
cursor.moveToFirst();
return cursor.getInt(0);
}
You can use the
if(cursor.moveToFirst())
{
//Your code here
}
instead off
cursor.getCount()
it will return true if cursor size is greater then 0 else it will return false........so you can write like this.........
if (!cursor.moveToFirst())
return -1;
else
return 1;
Use this instead of the return statement. The cursor is getting leaked since you are not closing it
try{
if (cursor.getCount() != 1) { //HERE IS THE PROBLEM
return -1;
}
int i = 0;
i++;
cursor.moveToFirst();
return cursor.getInt(0);
}finally{
if(cursor != null)
cursor.close();
}
Edit:
When you open a file from File manager, the uri will be of the form file:///sdcard/filename but the Mediastore can only understand uri of the format content://media/audio/1. This is the reason you are getting cursor as null.
one way is to query the whole Mediastore and get the MediaStore.Images.Media.DATA column and compare with the path that you get from uri