Confusion in ContentPorvider's CRUD operations - android

I am new to contenProvider and am reading a tutorial on how to do CRUD operations on UserDictionary.
For Query:
resolver.query(UserDictionary.Words.CONTENT_URI, projection, null, null, null);
For insert:
resolver.insert(UserDictionary.Words.CONTENT_URI, values); //ContentValues values = new ContentValues();
For update:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
long noUpdated = resolver.update(uri, values, null, null);
for delete:
long noDeleted = resolver.delete(Words.CONTENT_URI,
Words.WORD + " = ? ", new String[]{"Zaphod"});
My confusion is in update and delete operations.
In update: why is it using Words.CONTENT_URI in withAppendedId() ? shouldn't it be using UserDictionary.Words.CONTENT_URI ?
Also in delete: its not using withAppendedId(). Still why is it using Words.CONTENT_URI ?

In your example UserDictionary.Words.CONTENT_URI is what identifies the data in a provider, so the data location, the appended Id is what identifies a unique record.
So, by your examples:
1) For Query, is using UserDictionary.Words.CONTENT_URI to return all the records under this content Uri address. If you wanted to return only one specific record, from which you already know its Id, then it could also be as follows:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
resolver.query(uri, projection, null, null, null);
3) For Insert, no Id is used because the record doesn't exist yet. What the operation does is to insert a new record into the database, and the return value will be the new record Uri, which will include the new Id.
3) For Update, the appended Id is used to identify which specific record must be updated. Updating UserDictionary.Words.CONTENT_URI without a record Id would result in the update of all the records.
4) For Delete, your example is deleting all the records where the column Words.WORD has the value "Zaphod", which may result in 0, 1, or multiple records being deleted.
If you wanted to delete only 1 specific record from which you know its Id, then it would be as next:
Uri uri = ContentUris.withAppendedId(Words.CONTENT_URI, id);
resolver.delete(uri, null, null);
Answering your last question, there is no difference between Words.CONTENT_URI and UserDictionary.Words.CONTENT_URI. The UserDictionary section is a qualifier, which can be removed if has been added as an import on top of the java file, this allows not having to type it all the time, but at the end both are actually the same thing and makes no difference.

Related

Why none proposed solution is not working when I try to update a record in SQLite Database?

I am a novice user,I am trying to update a record with some fields and nothing special. I noticed! that this may be answered a lot of times but none of the proposed answers is working and I dont know where to check in my code to find the solution. I have the following :
public int updateUser(User user) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(U_ID, user.getId());
values.put(U_NAME, user.getName());
values.put(U_EMAIL, user.getEmail());
values.put(U_ZIP, user.getZip());
values.put(U_CREATED_AT, user.getCreated_at());
int res = db.update("login_user", values, "U_ID" + "=?", new String[] {String.valueOf(user.getId())});
return res;
}
I have tried
int res = db.update("login_user", values, "U_ID" + " = ?", new String[] {String.valueOf(user.getId())});
int res = db.update("login_user", values, "U_ID" + "=?", new String[] {(UserId)});
int res = db.update(MYTABLE, values, U_ID + "=?", new String[] {String.valueOf(user.getId())});
I increased my Database version to make it empty I saved a new record so
My Data are not null, but I get as res=0 and not an expected res=1 and with no errors
What I am doing wrong and where to look?
herein lies your problem
I increased my Database version to make it empty
assuming your database is empty, then you should use the insert method instead. updating an empty table will not have any effects because the WHERE condition in the update statement will always return false.
db.insertWithOnConflict (String table,
null, //nullColumnHack
values,
SQLiteDatabase.CONFLICT_REPLACE);
this will create an entry if the userId is not found, or replace the existing userId row if there is a conflict
The response from the Sqlite Update method is the number of rows updated.
If the record exists in the DB then it will return 1 (or more)
If you've updated the DB number all the records are delted - so verify the record exists 1st.
If it's returning 0 then I suspect you don't have the record in the DB in the first place - so 0 records are updated.
If you're expecting 1 - then first check if the record exists, via ADB - see this answer on how to check the contents of your DB.

album_info does not contain album_id column error

I'm creating a music player that populates an array list with song objects. Each song object has a series of attributes. One of those attributes is the album-art URI.
This is the line of code where I assign the URI attribute to the song object.
songObject.albumArtURI = GetAlbumArtURI(albumID);
Here is the string value of albumID
Log.v("TAG",String.valueOf(albumID)); // prints [Ljava.lang.String;#44ce53d
When I pass albumID to GetAlbumArtURI() method
private String GetAlbumArtURI(String[] albumID){
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums.ALBUM_ID + "=?",
albumID, // Error
null
);
return mCursor.getString(0);
}
I get this error:
no such column: album_id (code 1)
while compiling:
SELECT album_art FROM album_info WHERE (album_id=?)
The error essentially says that table album_info does not contain album_id column. But according to the documenation, album_info does have such a column.
So there's a few issues causing your query to return nothing and throw that error.
MediaStore.Audio.Albums.ALBUM_ID is not the column you want to reference. You should be using MediaStore.Audio.Albums._ID.
You need to move your cursor's read position to the first position when you get results, if possible. Doing otherwise will result in you never getting the results you need
The way that MediaStore works on android is that you have to register the media files that you want the OS to know about - this isn't automatic. You need to implement something similar to the SingleMediaScanner described in this thread
Here is the working bit of code that I have written:
try {
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums._ID + "=?",
null,
null
);
// You need to check if there are results in your cursor.
// This is like saying if(mCursor.getCount() > 0)
if(mCursor.moveToFirst()) {
return mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
} else {
return "";
}
} catch (Exception e) {
Log.e(this.getClass().getSimpleName(),e.getMessage());
}
When you have called that code, you're assuming that the MediaStore on your device knows about the music files you've downloaded or added. You can definitely implement a BroadcastReceiver to capture system events like files being added, but for this answer I'm just going to show how you account for one known file. You could also expand this to search an entire directory by adding to the onMediaScannerConnected(...) method.
If you implement the SingleMediaScanner file found here you can then just do:
File file = new File(Environment.getExternalStorageDirectory() + "/Download/1.mp3");
SingleMediaScanner singleMediaScanner = new SingleMediaScanner(this, file);
And it will register the media file in your MediaStore. At that point, you should be able to get results back from your query above. If you are having doubts of whether or not the songs are being registered, you can check to see if any records have been added at all by changing your mCursor call to this (to get all the results in the media store) and then iterating through them:
final Cursor mCursor = getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
null,
null,
null,
null
);

Android/SQLite: Insert-Update table columns to keep the identifier

Currently, I am using the following statement to create a table in an SQLite database on an Android device.
CREATE TABLE IF NOT EXISTS 'locations' (
'_id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT,
'latitude' REAL, 'longitude' REAL,
UNIQUE ( 'latitude', 'longitude' )
ON CONFLICT REPLACE );
The conflict-clause at the end causes that rows are dropped when new inserts are done that come with the same coordinates. The SQLite documentation contains further information about the conflict-clause.
Instead, I would like to keep the former rows and just update their columns. What is the most efficient way to do this in a Android/SQLite environment?
As a conflict-clause in the CREATE TABLE statement.
As an INSERT trigger.
As a conditional clause in the ContentProvider#insert method.
... any better you can think off
I would think it is more performant to handle such conflicts within the database. Also, I find it hard to rewrite the ContentProvider#insert method to consider the insert-update scenario. Here is code of the insert method:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long id = db.insert(DatabaseProperties.TABLE_NAME, null, values);
return ContentUris.withAppendedId(uri, id);
}
When data arrives from the backend all I do is inserting the data as follows.
getContentResolver.insert(CustomContract.Locations.CONTENT_URI, contentValues);
I have problems figuring out how to apply an alternative call to ContentProvider#update here. Additionally, this is not my favored solution anyways.
Edit:
#CommonsWare: I tried to implement your suggestion to use INSERT OR REPLACE. I came up with this ugly piece of code.
private static long insertOrReplace(SQLiteDatabase db, ContentValues values, String tableName) {
final String COMMA_SPACE = ", ";
StringBuilder columnsBuilder = new StringBuilder();
StringBuilder placeholdersBuilder = new StringBuilder();
List<Object> pureValues = new ArrayList<Object>(values.size());
Iterator<Entry<String, Object>> iterator = values.valueSet().iterator();
while (iterator.hasNext()) {
Entry<String, Object> pair = iterator.next();
String column = pair.getKey();
columnsBuilder.append(column).append(COMMA_SPACE);
placeholdersBuilder.append("?").append(COMMA_SPACE);
Object value = pair.getValue();
pureValues.add(value);
}
final String columns = columnsBuilder.substring(0, columnsBuilder.length() - COMMA_SPACE.length());
final String placeholders = placeholderBuilder.substring(0, placeholdersBuilder.length() - COMMA_SPACE.length());
db.execSQL("INSERT OR REPLACE INTO " + tableName + "(" + columns + ") VALUES (" + placeholders + ")", pureValues.toArray());
// The last insert id retrieved here is not safe. Some other inserts can happen inbetween.
Cursor cursor = db.rawQuery("SELECT * from SQLITE_SEQUENCE;", null);
long lastId = INVALID_LAST_ID;
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
lastId = cursor.getLong(cursor.getColumnIndex("seq"));
}
cursor.close();
return lastId;
}
When I check the SQLite database, however, equal columns are still removed and inserted with new ids. I do not understand why this happens and thought the reason is my conflict-clause. But the documentation states the opposite.
The algorithm specified in the OR clause of an INSERT or UPDATE
overrides any algorithm specified in a CREATE TABLE. If no algorithm
is specified anywhere, the ABORT algorithm is used.
Another disadvantage of this attempt is that you loose the value of the id which is return by an insert statement. To compensate this, I finally found an option to ask for the last_insert_rowid. It is as explained in the posts of dtmilano and swiz. I am, however, not sure if this is safe since another insert can happen inbetween.
I can understand the perceived notion that it is best for performance to do all this logic in SQL, but perhaps the simplest (least code) solution is the best one in this case? Why not attempt the update first, and then use insertWithOnConflict() with CONFLICT_IGNORE to do the insert (if necessary) and get the row id you need:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
//Do an update if the constraints match
db.update(DatabaseProperties.TABLE_NAME, values, selection, null);
//This will return the id of the newly inserted row if no conflict
//It will also return the offending row without modifying it if in conflict
long id = db.insertWithOnConflict(DatabaseProperties.TABLE_NAME, null, values, CONFLICT_IGNORE);
return ContentUris.withAppendedId(uri, id);
}
A simpler solution would be to check the return value of update() and only do the insert if the affected count was zero, but then there would be a case where you could not obtain the id of the existing row without an additional select. This form of insert will always return to you the correct id to pass back in the Uri, and won't modify the database more than necessary.
If you want to do a large number of these at once, you might look at the bulkInsert() method on your provider, where you can run multiple inserts inside a single transaction. In this case, since you don't need to return the id of the updated record, the "simpler" solution should work just fine:
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = null;
int rowsAdded = 0;
long rowId;
db.beginTransaction();
try {
for (ContentValues cv : values) {
selectionArgs = new String[] {cv.getAsString("latitude"),
cv.getAsString("longitude")};
int affected = db.update(DatabaseProperties.TABLE_NAME,
cv, selection, selectionArgs);
if (affected == 0) {
rowId = db.insert(DatabaseProperties.TABLE_NAME, null, cv);
if (rowId > 0) rowsAdded++;
}
}
db.setTransactionSuccessful();
} catch (SQLException ex) {
Log.w(TAG, ex);
} finally {
db.endTransaction();
}
return rowsAdded;
}
In truth, the transaction code is what makes things faster by minimizing the number of times the database memory is written to the file, bulkInsert() just allows multiple ContentValues to be passed in with a single call to the provider.
One solution is to create a view for the locations table with a INSTEAD OF trigger on the view, then insert into the view. Here's what that would look like:
View:
CREATE VIEW locations_view AS SELECT * FROM locations;
Trigger:
CREATE TRIGGER update_location INSTEAD OF INSERT ON locations_view FOR EACH ROW
BEGIN
INSERT OR REPLACE INTO locations (_id, name, latitude, longitude) VALUES (
COALESCE(NEW._id,
(SELECT _id FROM locations WHERE latitude = NEW.latitude AND longitude = NEW.longitude)),
NEW.name,
NEW.latitude,
NEW.longitude
);
END;
Instead of inserting into the locations table, you insert into the locations_view view. The trigger will take care of providing the correct _id value by using the sub-select. If, for some reason, the insert already contains an _id the COALESCE will keep it and override an existing one in the table.
You'll probably want to check how much the sub-select affects performance and compare that to other possible changes you could make, but it does allow you keep this logic out of your code.
I tried some other solutions involving triggers on the table itself based on INSERT OR IGNORE, but it seems that BEFORE and AFTER triggers only trigger if it will actually insert into the table.
You might find this answer helpful, which is the basis for the trigger.
Edit: Due to BEFORE and AFTER triggers not firing when an insert is ignored (which could then have been updated instead), we need to rewrite the insert with an INSTEAD OF trigger. Unfortunately, those don't work with tables - we have to create a view to use it.
INSERT OR REPLACE works just like ON CONFLICT REPLACE. It will delete the row if the row with the unique column already exists and than it does an insert. It never does update.
I would recommend you stick with your current solution, you create table with ON CONFLICT clausule, but every time you insert a row and the constraint violation occurs, your new row will have new _id as origin row will be deleted.
Or you can create table without ON CONFLICT clausule and use INSERT OR REPLACE, you can use insertWithOnConflict() method for that, but it is available since API level 8, requires more coding and leads to the same solution as table with ON CONFLICT clausule.
If you still want to keep your origin row, it means you want to keep the same _id you will have to make two queries, first one for inserting a row, second to update a row if insertion failed (or vice versa). To preserve consistency, you have to execute queries in a transaction.
db.beginTransaction();
try {
long rowId = db.insert(table, null, values);
if (rowId == -1) {
// insertion failed
String whereClause = "latitude=? AND longitude=?";
String[] whereArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
db.update(table, values, whereClause, whereArgs);
// now you have to get rowId so you can return correct Uri from insert()
// method of your content provider, so another db.query() is required
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
Use insertWithOnConflict and set the last parameter (conflictAlgorithm) to CONFLICT_REPLACE.
Read more at the following links:
insertWithOnConflict documentation
CONFLICT_REPLACE flag
for me, none of the approaches are work if I don't have "_id"
you should first call update, if the affected rows are zero, then insert it with ignore:
String selection = MessageDetailTable.SMS_ID+" =?";
String[] selectionArgs = new String[] { String.valueOf(md.getSmsId())};
int affectedRows = db.update(MessageDetailTable.TABLE_NAME, values, selection,selectionArgs);
if(affectedRows<=0) {
long id = db.insertWithOnConflict(MessageDetailTable.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
Use INSERT OR REPLACE.
This is the correct way to do it.

Query, backup, delete, insert Contacts in Android

This question should be a starting point to all of us who want to manipulate contacts in Android.
First things first
As I am aware, since API level 5 the Contacts API has changed, so in order to make the application work correct I need to check what android os is on the phone and if prior 5 use one content provider or else use the newer one. The only annoyance in this case is the warnings of deprecated I get. The application is build against Android 2.3.3 but needs to work from 1.5+
1. Querying contacts
This is the easiest part to do. Usually querying means getting data like Contact name, phones, picture, email and displaying it on a listview. For instance here is how I've done it in API prior 5
String[] projectionPeople = new String[] {People._ID, People.NAME,};
String[] projectionPhone = new String[] {Phones.NUMBER};
try {
// Get the base URI for People table in Contacts content provider.
// which is: content://contacts/people/
Uri contactUri = People.CONTENT_URI;
ContentResolver resolver = getContentResolver();
Cursor phonesCursor = null;
Cursor peopleCursor = resolver.query (contactUri,
projectionPeople, //Which columns to return.
"People.NAME is not null", // WHERE clause--we won't specify.
null, // Selection Args??
People.DEFAULT_SORT_ORDER); // Order-by name
if (peopleCursor != null && peopleCursor.getCount() >0)
{
// go to the beginning of the list
peopleCursor.moveToFirst();
do
{
//do something with current contact info
phoneUri= Uri.withAppendedPath(personUri, Contacts.People.Phones.CONTENT_DIRECTORY);
phonesCursor = resolver.query(phoneUri,
projectionPhone,
null,
null,
Phones.DEFAULT_SORT_ORDER);
if (phonesCursor!=null && phonesCursor.getCount()>0)
{
phonesCursor.moveToFirst();
lstPhones = new ArrayList<String>();
do
{
//add phone numbers to a List<String> for instance
} while (phonesCursor.moveToNext());
if (phonesCursor != null && !phonesCursor.isClosed())
phonesCursor.close();
} while (peopleCursor.moveToNext());
if (peopleCursor != null && !peopleCursor.isClosed())
peopleCursor.close();
}
}
catch (Exception ex)
{
}
}
Haven't tried it yet on the new api but the cursor should be like
final String[] projection = new String[] {
RawContacts.CONTACT_ID, // the contact id column
RawContacts.DELETED // column if this contact is deleted
};
final Cursor rawContacts = managedQuery(RawContacts.CONTENT_URI, // the URI for raw contact provider
projection
null, // selection = null, retrieve all entries
null, // selection is without parameters
null); // do not order
Sure, this needs to be elaborated a bit more, but it should provide the basics of simple query against Contacts content provider
2. Backup
My first thought on this was: if I know the Id of a Contact, I create tables in a sqlite database exactly how the cursor columns are and insert all the data into my tables. This is not an easy task as it requires a lot of codding not to mention that different apis have different table structures. What would be the best solution to backup one contact or multiple contacts ?
3. Delete
This should work on all apis using content providers, but data is spread on many packages and uris and I'm not sure from where to delete
4. Insert
After a contact is backed up, I may need to restore/insert it again. As in case of deletion, on which uris do I need to insert ?
Please, let's try to elaborate this issues so in the futures, who needs to use Contacts in Android apps could take this question as a solid starting point. Thank you stackoverflow community.
Here is a good starting point
http://developer.android.com/resources/samples/BusinessCard/index.html

Calling delete method in custom content provider

I am learning Android and I am stuck on an issue involving calling a custom content provider. I have been using an example in an instructional book and although it describes how to create the custom provider there is no clear example how to call the specific methods in it. I am specifically looking into how to delete a single record from the custom content provider.
Here is the code for the custom content provider (EarthquakeProvider.java):
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case QUAKES:
count = earthquakeDB.delete(EARTHQUAKE_TABLE, where, whereArgs);
break;
case QUAKE_ID:
String segment = uri.getPathSegments().get(1);
count = earthquakeDB.delete(EARTHQUAKE_TABLE, KEY_ID + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
I am trying to call the delete method from the main activity to delete a single entry, not the entire database. I want to use about an OnLongClickListener for the selected record that is displayed in a array list view in the main activity.
This is what I have come up with I have so far in my main activity for this method:
earthquakeListView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView _av, View _v, int _index,
long arg3) {
ContentResolver cr = getContentResolver();
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
return false;
}
I know the above code doesn't work, but this is as close as I could get with my current understanding.
Any help on this would be very much appreciated.
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
This is your problem. First, some context:
Content URIs: (source)
content://authority/path/##
The number at the end is optional. If present, the URI references a specific row in the database where row._id=(the number). If absent, it references the table as a whole.
the delete() call accepts a URI, a where clause, and a set of strings which get substituted in. Example: Say you have a database of people.
cr.delete(
Person.CONTENT_URI,
"sex=? AND eyecolor=?",
new String[]{"male", "blue"});
Will search the entire person table, and delete anyone whose sex is male and whose eye color is blue.
If the where clause and where values are null, then the delete() call will match every row in the table. This causes the behavior you see.
There are two methods to specify the row you want:
First option, you could append the number to the URI:
cr.delete(
EarthquakeProvider.CONTENT_URI.buildUpon().appendPath(String.valueOf(_id)).build(),
null, null);
This restricts the URI to a specific row, and the path will be through your case QUAKE_ID: statement and so will only delete one row no matter what.
Second option, you could use a where clause:
cr.delete(EarthquakeProvider.CONTENT_URI, "_id=?", String.valueOf(_id)));
Either way, you will restrict the delete to a single row, as you need it to. The latter makes for prettier code, but the former is more efficient, due to the way the ContentProvider and ContentObservers work.
As a last note: In your ContentProvider you need to add a call to
ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork). This helps notify cursors to re-fetch the database query and helps out a lot with automation.

Categories

Resources