Android Contentprovider - update within an insert method - android

Is it ok to call the SQLiteDatabase update method in the insert() overridden method of a content provider?

Basically it's fine, but since you didn't provided code, I just can post 2 possible ways for it
First:
// In your content provider
public Uri insert(...) {
long insertId = db.insert(...);
if(insertId == -1) {
// insert failed, do update
db.update(...);
}
}
Second:
public Uri insert(...) {
long insertId = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE)
if(insertId == -1) {
// insert and update/replace failed
}
}
Check out SQLiteDatabase for reference on the forth parameter. In most cases the last method should be sufficient, unless you want only certain fields being updated if the row exists and have all fields if it doesn't.
Most useful need for insertWithOnConflict may be, that you could insert a row and if it already exists, ignore the insert and instead return the Uri/primary key of the already existing row.

It's your choice what you write in your overridden methods.
So yes, it is ok.
I don't know what you're trying to do, but you might want to to take a look on the SQLiteDatabase's replace() method too. Maybe it better suits your needs.

Related

Android SQLite batch insertion

I need to insert a lot of rows in the database at a time and I'm seeking for the most efficient way to do it. I have seen code like this:
db.beginTransaction();
for (ModelObject object : modelObjectsCollection){
ContentValues values = new ContentValues();
... // fill values variable with values from object
db.insert(TABLE_NAME, null, values);
values.clear();
}
db.setTransactionSuccessful();
In this case all inserts are accomplished as a single operation which takes less time. Will it still work as a single operation if I incapsulate insertion of a single row into a method like this:
public void insertAllRows(){
db.beginTransaction();
for (ModelObject object : modelObjectsCollection){
insertSingleRow(object);
}
db.setTransactionSuccessful();
}
public void insertSingleRow(ModelObject object){
ContentValues values = new ContentValues();
... // fill values variable with values from object
db.insert(TABLE_NAME, null, values);
}
Will it be accomplished in a single transaction as well?
Besides, I do not understand: is it correct that if we do not call
db.beginTransaction(); ... db.setTransactionSuccessful();
explicitly, but only call db.insert(), beginTransaction()-setTransactionSuccessful() methods are invoked inside insert(). In contrary, if we invoke insert() between invokations of beginTransaction()-setTransactionSuccessful(), the latter 2 methods aren't invoked inside insert()?
You can execute queries without transactions at all. And there are no need in transaction for single query at all.
Also you need to call db.endTransaction(); after db.setTransactionSuccessful() for commiting it.
upd: you can find more about common transaction use here http://www.4js.com/online_documentation/fjs-fgl-manual-html/User/Transactions.html for example.

Refresh/Reload database reference in custom ContentProvider after restore

I use a ContentProvider in my app and everything works great except for one little issue. I have a backup and restore function that backs up the database to a file on the SD card and then those backup files can be restored to overwrite the current database. This whole process is working, but the ContentProvider still holds the reference/cache to the original database once one of the old backup files is restored. I can't seem to find a way to refresh or reload the database reference in the ContentProvider. I know the restore works because I can see the records in the db with SQLite Editor and when I close and re-open the app, it displays the correct records.
Does anybody know a way to do this? Is there a way to close and re-open the ContentProvider that I'm not seeing?
If you are targeting >= API 5 you can get a reference to your ContentProvider via a ContentProviderClient, and run a method specific to your implementation:
ContentResolver resolver = context.getContentResolver();
ContentProviderClient client = resolver.acquireContentProviderClient("myAuthority");
MyContentProvider provider = (MyContentProvider) client.getLocalContentProvider();
provider.resetDatabase();
client.release();
Add the reset method to your ContentProvider implementation:
public void resetDatabase() {
mDatabaseHelper.close();
mDatabaseHelper = new MyDatabaseOpenHelper(context);
}
Are you maintaining a reference to the actual SQLiteDatabase in your content provider (something like calling SQLiteOpenHelper.getWritableDatabase() in onCreate() and then keeping that reference)? Or do you get the DB object from someplace like a helper in each provider method?
Typically, if you only keep a local reference to the helper and get the writable/readable database instance inside of each method as needed then this problem should go away. If not, perhaps we can take a look at the provider code?
Hope that Helps!
Here is my solution.
public class DataProvider extends ContentProvider {
private DataDbHelper dbHelper;
#Override
public boolean onCreate() {
// nothing here
return true;
}
private DataDbHelper getDbHelper() {
if (dbHelper== null) {
// initialize
dbHelper = new DataDbHelper(getContext());
} else if (dbHelper.getReadableDatabase().getVersion() != DataDbHelper.VERSION) {
// reset
dbHelper.close();
dbHelper = new DataDbHelper(getContext());
}
return this.mOpenHelper;
}
}
query(), insert(), update(), delete() use getDbHelper() to obtain an SQLiteDatabase
The full code of my Android app is available here if you need more info.
You can also simply use the delete method without a selection:
context.getContentResolver().delete(YourProvider.CONTENT_URI, null, null);

Android Cursor with ORMLite to use in CursorAdapter

Is there any way to get Cursor for a query, which I am processing with ORMLite Dao object?
ORMLite now supports next(), previous(), moveRelative(offset), ... methods on the CloseableIterator class. This should allow you to move the underlying Cursor object around at will.
It also supports the following DAO Cursor methods:
dao.mapSelectStarRow(databaseResults) Return the latest row from the database results from a query to select *. With this you can change the cursor location (for example) and then get the current object.
dao.getSelectStarRowMapper() Provides a mapper that you can use to map the object outside of the Dao.
When you are building your own query with ORMLite, you use the QueryBuilder object. queryBuilder.prepare() returns a PreparedQuery which is used by various methods in the DAO. You can call dao.iterator(preparedQuery) which will return a CloseableIterator which is used to iterate through the results. There is a iterator.getRawResults() to get access to the DatabaseResults class. Under Android, this can be cast to an AndroidDatabaseResults which has a getCursor() method on it to return the Android Cursor.
Something like the following code:
// build your query
QueryBuilder<Foo, String> qb = fooDao.queryBuilder();
qb.where()...;
// when you are done, prepare your query and build an iterator
CloseableIterator<Foo> iterator = dao.iterator(qb.prepare());
try {
// get the raw results which can be cast under Android
AndroidDatabaseResults results =
(AndroidDatabaseResults)iterator.getRawResults();
Cursor cursor = results.getRawCursor();
...
} finally {
iterator.closeQuietly();
}
This is a bit complicated but you are definitely having to peer behind the vale to get to this object which is hidden by the database abstraction classes.
Did you try some of Gray's advice from this post? He explains how you can select a column as another name, such as, select id as _id.
If you're in an Activity and don't want to mess around with the QueryBuilder give the following a go, which is just as effective.
Cursor cursor = getHelper().getReadableDatabase().query(tableName, projection, selection, selectionArgs, groupBy, having, sortOrder)
If you mean the getHelper() method to reach the dao methods create etc. you only have to inherit from the OrmLiteBaseActivity<YourDBHelper> and you can call it. It will look sth like this:
public class YourClass extends OrmLiteBaseActivity<YourDBHelper> {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
getHelper().getDao().queryForAll();
...
}
}
If you mean the cursor to handle database operation: I don't think that you can reach it! But I don't understand why you should need it. ORMLite has nearly all functions of the cursor. So what do you need it for?

Is Android Cursor.moveToNext() Documentation Correct?

boolean android.database.Cursor.moveToNext() documentation says:
http://developer.android.com/reference/android/database/Cursor.html#moveToNext%28%29
Move the cursor to the next row.
This method will return false if the cursor is already past the last entry in the result set.
However, my book says to do the following to extract data from a cursor:
Cursor myCursor = myDatabase.query(...);
if (myCursor.moveToFirst()) {
do {
int value = myCursor.getInt(VALUE_COL);
// use value
} while (myCursor.moveToNext());
}
Who's right? These both can't be true. If you can't see the contradiction, imagine myCursor has 1 row returned from the query. The first call to getInt() will work, but then moveToNext() will return true because it is not "already" past the last entry in the result set. So now the cursor will be past the last entry and the second call to getInt() will do something undefined.
I suspect the documentation is wrong and should instead read:
This method will return false if the cursor is "already at" the last entry in the result set.
Must the cursor be already PAST (not AT) the last entry before the moveToNext() method returns false?
No Snark Please
A simpler idiom is:
Cursor cursor = db.query(...);
while (cursor.moveToNext()) {
// use cursor
}
This works because the initial cursor position is -1, see the Cursor.getPosition() docs.
You can also find usages of cursors in the Android source code itself with this Google Code Search query. Cursor semantics are the same in SQLite database and content providers.
References: this question.
Verbatim from the API:
Returns:
whether the move succeeded.
So, it means that:
Cursor in first row -> moveToNext() -> cursor in second row -> there's no second row -> return false
If you want the details, go to the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/database/AbstractCursor.java#AbstractCursor.moveToNext%28%29
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
I think I tend to stay away from solutions that are based on a hidden assumption like: I hope that sqlite never changes it api and that a cursor will always start at the first item.
Also, I have almost always been able to replace a while statement with a for statement. So my solution, shows what I expect the cursor to start at, and avoids using a while statement:
for( boolean haveRow = c.moveToFirst(); haveRow; haveRow = c.moveToNext() ) {
...
}
why is showing that a cursor needs to start at the first row, well 6 months down the line you might be debugging your own code, and will wonder why you didn't make that explicit so you could easily debug it.
It appears to be down to the Android implementation of AbstractCursor and it remains broken in Jellybean.
I implemented the following unit test to demonstrate the problem to myself using a MatrixCursor:
#Test
public void testCursor() {
MatrixCursor cursor = new MatrixCursor(new String[] { "id" });
for (String s : new String[] { "1", "2", "3" }) {
cursor.addRow(new String[] { s });
}
cursor.moveToPosition(0);
assertThat(cursor.moveToPrevious(), is(true));
cursor.moveToPosition(cursor.getCount()-1);
assertThat(cursor.moveToNext(), is(true));
assertThat(cursor.moveToPosition(c.getCount()), is(true));
assertThat(cursor.moveToPosition(-1), is(true));
}
All assertions fail, contrary to the documentation for moveToNext, moveToPrevious and moveToPosition.
Reading the code at API 16 for AbstractCursor.moveToPosition(int position) it appears to be intentional behaviour, ie the methods explicitly return false in these cases, contrary to the documentation.
As a side note, since the Android code sat on existing devices in the wild cannot be changed, I have taken the approach of writing my code to match the behaviour of the existing Android implementation, not the documentation. ie. When implementing my own Cursors / CursorWrappers, I override the methods and write my own javadoc describing the departure from the existing documentation. This way, my Cursors / CursorWrappers remain interchangeable with existing Android cursors without breaking run-time behaviour.
Cursor.moveToNext() returning a boolean is only useful if it will not move the cursor past the last entry in the data set. Thus, I have submitted a bug report on the documentation's issue tracker.
https://issuetracker.google.com/issues/69259484
It reccomends the following sentence:
"This method will return false if the current (at time of execution) entry is the last entry in the set, and there is no next entry to be had."

What to do in custom ContentProvider's fillWindow() method?

I'm writing a custom ContentProvider that serves up content consisting of a single, constant string which I represent as a one-row table having columns _id = 0 and value = "SomeString". This string is not stored in a database, so I developed a subclass of CrossProcessCursor that has does everything required to behave like what I described above.
The documentation for CrossProcessCursor is very sparse and doesn't really explain what the fillWindow() method should be doing beyond the obvious. Based on the descriptions of CursorWindow's methods, I put the following together, which I thought should cover it:
public class MyCursor implements CrossProcessCursor {
...
public void fillWindow(int pos, CursorWindow window) {
if (pos != 0) { // There's only one row.
return;
}
window.clear();
window.allocRow(); // TODO: Error check, false = no memory
window.setNumColumns(2);
window.setStartPosition(0);
window.putLong(0, 0, 0);
window.putString("SomeString", 0, 1);
}
}
As expected, it gets called with pos = 0 when a client application requests the content, but the client application throws an exception when it tries to go after the first (and only) row:
Caused by: java.lang.IllegalStateException: UNKNOWN type 48
at android.database.CursorWindow.getLong_native(Native Method)
at android.database.CursorWindow.getLong(CursorWindow.java:380)
at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:108)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:194)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:248)
at android.database.CursorWrapper.moveToFirst(CursorWrapper.java:86)
...(Snipped)...
Could anyone shed some light on what this method should be doing to return a correct-looking row to the client?
Thanks.
For what you're doing you should check out the MatrixCursor. It uses the AbstractCursor#fillWindow implementation which calls toString on every object. Since you're just sending a string anyway it should work fine for you.

Categories

Resources